Full Code of A7Lavinraj/fyler.nvim for AI

main e87911e6c21d cached
214 files
694.2 KB
173.6k tokens
1 requests
Download .txt
Showing preview only (780K chars total). Download the full file or copy to clipboard to get everything.
Repository: A7Lavinraj/fyler.nvim
Branch: main
Commit: e87911e6c21d
Files: 214
Total size: 694.2 KB

Directory structure:
gitextract_2i4brai5/

├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug-report.yml
│   │   ├── config.yml
│   │   └── feature-request.yml
│   ├── pull_request_template.md
│   └── workflows/
│       └── ci.yaml
├── .gitignore
├── .luarc.json
├── .markdownlint.json
├── .stylua.toml
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── bin/
│   ├── gen_vimdoc.lua
│   └── run_tests.lua
├── doc/
│   └── fyler.txt
├── lua/
│   ├── fyler/
│   │   ├── autocmds.lua
│   │   ├── config.lua
│   │   ├── deprecated.lua
│   │   ├── hooks.lua
│   │   ├── input.lua
│   │   ├── inputs/
│   │   │   ├── confirm.lua
│   │   │   └── winpick.lua
│   │   ├── integrations/
│   │   │   ├── icon/
│   │   │   │   ├── init.lua
│   │   │   │   ├── mini_icons.lua
│   │   │   │   ├── nvim_web_devicons.lua
│   │   │   │   └── vim_nerdfont.lua
│   │   │   └── winpick/
│   │   │       ├── init.lua
│   │   │       ├── nvim_window_picker.lua
│   │   │       └── snacks.lua
│   │   ├── lib/
│   │   │   ├── async.lua
│   │   │   ├── diagnostic.lua
│   │   │   ├── fs.lua
│   │   │   ├── git.lua
│   │   │   ├── hl.lua
│   │   │   ├── path.lua
│   │   │   ├── process.lua
│   │   │   ├── spinner.lua
│   │   │   ├── structs/
│   │   │   │   ├── list.lua
│   │   │   │   ├── stack.lua
│   │   │   │   └── trie.lua
│   │   │   ├── trash/
│   │   │   │   ├── init.lua
│   │   │   │   ├── linux.lua
│   │   │   │   ├── macos.lua
│   │   │   │   └── windows.lua
│   │   │   ├── ui/
│   │   │   │   ├── component.lua
│   │   │   │   ├── init.lua
│   │   │   │   └── renderer.lua
│   │   │   ├── util.lua
│   │   │   └── win.lua
│   │   ├── log.lua
│   │   └── views/
│   │       └── finder/
│   │           ├── actions.lua
│   │           ├── files/
│   │           │   ├── init.lua
│   │           │   ├── manager.lua
│   │           │   └── resolver.lua
│   │           ├── helper.lua
│   │           ├── indent.lua
│   │           ├── init.lua
│   │           ├── ui.lua
│   │           └── watcher.lua
│   ├── fyler.lua
│   └── telescope/
│       └── _extensions/
│           └── fyler.lua
├── plugin/
│   └── fyler.lua
├── selene/
│   ├── config.toml
│   └── globals.toml
├── syntax/
│   └── fyler.lua
└── tests/
    ├── README.md
    ├── helper.lua
    ├── integration/
    │   ├── test_finder_mappings.lua
    │   └── test_finder_mutation.lua
    ├── minimal_init.lua
    ├── screenshots/
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'float'-}
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'float'-}-002
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'float'-}-003
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'float'-}-004
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'float'-}-005
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'float'-}-006
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'float'-}-007
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'float'-}-008
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'float'-}-009
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'replace'-}
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'replace'-}-002
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'replace'-}-003
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'replace'-}-004
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'replace'-}-005
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'replace'-}-006
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'replace'-}-007
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'replace'-}-008
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'replace'-}-009
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above'-}
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above'-}-002
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above'-}-003
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above'-}-004
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above'-}-005
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above'-}-006
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above'-}-007
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above'-}-008
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above'-}-009
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above_all'-}
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above_all'-}-002
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above_all'-}-003
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above_all'-}-004
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above_all'-}-005
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above_all'-}-006
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above_all'-}-007
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above_all'-}-008
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above_all'-}-009
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below'-}
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below'-}-002
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below'-}-003
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below'-}-004
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below'-}-005
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below'-}-006
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below'-}-007
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below'-}-008
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below'-}-009
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below_all'-}
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below_all'-}-002
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below_all'-}-003
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below_all'-}-004
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below_all'-}-005
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below_all'-}-006
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below_all'-}-007
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below_all'-}-008
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below_all'-}-009
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left'-}
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left'-}-002
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left'-}-003
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left'-}-004
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left'-}-005
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left'-}-006
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left'-}-007
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left'-}-008
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left'-}-009
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left_most'-}
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left_most'-}-002
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left_most'-}-003
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left_most'-}-004
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left_most'-}-005
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left_most'-}-006
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left_most'-}-007
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left_most'-}-008
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left_most'-}-009
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right'-}
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right'-}-002
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right'-}-003
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right'-}-004
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right'-}-005
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right'-}-006
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right'-}-007
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right'-}-008
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right'-}-009
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right_most'-}
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right_most'-}-002
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right_most'-}-003
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right_most'-}-004
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right_most'-}-005
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right_most'-}-006
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right_most'-}-007
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right_most'-}-008
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right_most'-}-009
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Navigate-+-args-{-'float'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Navigate-+-args-{-'replace'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Navigate-+-args-{-'split_above'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Navigate-+-args-{-'split_above_all'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Navigate-+-args-{-'split_below'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Navigate-+-args-{-'split_below_all'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Navigate-+-args-{-'split_left'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Navigate-+-args-{-'split_left_most'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Navigate-+-args-{-'split_right'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Navigate-+-args-{-'split_right_most'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-With-Arguments-+-args-{-'float'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-With-Arguments-+-args-{-'replace'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-With-Arguments-+-args-{-'split_above'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-With-Arguments-+-args-{-'split_above_all'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-With-Arguments-+-args-{-'split_below'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-With-Arguments-+-args-{-'split_below_all'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-With-Arguments-+-args-{-'split_left'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-With-Arguments-+-args-{-'split_left_most'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-With-Arguments-+-args-{-'split_right'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-With-Arguments-+-args-{-'split_right_most'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-Without-Arguments-+-args-{-'float'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-Without-Arguments-+-args-{-'replace'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-Without-Arguments-+-args-{-'split_above'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-Without-Arguments-+-args-{-'split_above_all'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-Without-Arguments-+-args-{-'split_below'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-Without-Arguments-+-args-{-'split_below_all'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-Without-Arguments-+-args-{-'split_left'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-Without-Arguments-+-args-{-'split_left_most'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-Without-Arguments-+-args-{-'split_right'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-Without-Arguments-+-args-{-'split_right_most'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'float'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'float'-}-002
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'replace'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'replace'-}-002
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_above'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_above'-}-002
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_above_all'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_above_all'-}-002
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_below'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_below'-}-002
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_below_all'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_below_all'-}-002
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_left'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_left'-}-002
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_left_most'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_left_most'-}-002
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_right'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_right'-}-002
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_right_most'-}
    │   └── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_right_most'-}-002
    └── unit/
        ├── test_api.lua
        ├── test_mini_icon.lua
        └── test_setup.lua

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

================================================
FILE: .github/ISSUE_TEMPLATE/bug-report.yml
================================================
name: Bug report
description: Report a problem to help us improve
title: "[BUG] "
labels: [bug]
body:
  - type: checkboxes
    id: guidelines
    attributes:
      label: Report guidelines
      options:
        - label: I have updated 'fyler.nvim' to latest version of the `main` branch
          required: true
  - type: dropdown
    id: branch
    attributes:
      label: "Branch"
      description: "Choose branch in which you encounter this bug"
      options:
        - main
        - stable
    validations:
      required: true
  - type: dropdown
    id: os
    attributes:
      label: "OS"
      description: "Choose your OS"
      options:
        - Linux
        - MacOS
        - Windows
    validations:
      required: true
  - type: input
    id: nvim-version
    attributes:
      label: "Neovim version"
      description: "Write the latest Neovim version on which you can reproduce the problem"
    validations:
      required: true
  - type: textarea
    id: description
    attributes:
      label: "Description"
      description: "A short description of a problem; include expected behavior"
    validations:
      required: true
  - type: textarea
    id: reproduction
    attributes:
      label: "Reproduction"
      description: "Steps to reproduce the issue."
    validations:
      required: true


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
  - name: Question
    url: https://github.com/A7Lavinraj/fyler.nvim/discussions
    about: Usage questions and support requests are answered in the discussions


================================================
FILE: .github/ISSUE_TEMPLATE/feature-request.yml
================================================
name: Feature request
description: Describe a feature you would like to see
title: "[FEAT] "
labels: [feature request]
body:
  - type: textarea
    id: description
    attributes:
      label: "Description"
      description: "A short description of a feature request"
    validations:
      required: true


================================================
FILE: .github/pull_request_template.md
================================================
- [ ] I have read and follow [CONTRIBUTING.md](https://github.com/A7Lavinraj/fyler.nvim/blob/main/CONTRIBUTING.md)


================================================
FILE: .github/workflows/ci.yaml
================================================
name: CI
on:
  - push
  - pull_request
jobs:
  format:
    name: Formatting
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: JohnnyMorganz/stylua-action@v3
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          version: latest
          args: --color always --check lua/
  lint:
    name: Linting
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: Swatinem/rust-cache@v2
      - uses: taiki-e/install-action@v2
        with:
          tool: selene
      - name: run linters
        run: make lint
  test:
    name: Testing
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: folke/github/neovim@main
      - name: Running tests
        run: make test


================================================
FILE: .gitignore
================================================
# Temporary test directory
.temp

# Quick testing directory for local development
.scratch

# Take notes for any potential thing to remember
.notes

# Docs helptags file
doc/tags


================================================
FILE: .luarc.json
================================================
{
    "$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
    "diagnostics.disable": [ "redefined-local" ],
    "diagnostics.globals": [ "vim" ],
    "workspace.checkThirdParty": false,
    "runtime.version": "LuaJIT"
}


================================================
FILE: .markdownlint.json
================================================
{
  "MD033": false,
  "MD041": false,
  "MD013": false
}


================================================
FILE: .stylua.toml
================================================
collapse_simple_statement = "Always"
column_width = 120
indent_type = "Spaces"
indent_width = 2
line_endings = "Unix"
quote_style = "AutoPreferDouble"
syntax = "LuaJIT"

[sort_requires]
enabled = true


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing Guide

We welcome contributions! Here's how to get started:

## Getting Started

- Fork the repository
- Clone your fork locally:  

```sh
git clone https://github.com/your-username/repo-name.git
```

- Create a new branch:  

```sh
git checkout -b my-feature-branch
```

## Development

- Add cloned repository to neovim runtime path

## Pull Requests

1. Keep PRs focused on a single feature/bugfix
2. Write clear commit messages
3. Update documentation if needed
4. Ensure tests pass

## Code Style

- Follow existing code conventions
- Keep code clean
- Use descriptive variable names

## Reporting Issues

Include:

- Expected vs. Actual behavior
- Steps to reproduce

Thank you for contributing!


================================================
FILE: LICENSE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [2025] [Lavin Raj Mohan]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: Makefile
================================================
.SILENT:

.PHONY: init
init: format lint test doc

.PHONY: deps
deps:
	@if [ ! -d .temp/deps/mini.test ]; then \
		echo "Cloning mini.test"; \
		git clone --depth=1 --single-branch https://github.com/nvim-mini/mini.test .temp/deps/mini.test; \
	fi
	@if [ ! -d .temp/deps/mini.icons ]; then \
		echo "Cloning mini.icons"; \
		git clone --depth=1 --single-branch https://github.com/nvim-mini/mini.icons .temp/deps/mini.icons; \
	fi
	@if [ ! -d .temp/deps/mini.doc ]; then \
		echo "Cloning mini.doc"; \
		git clone --depth=1 --single-branch https://github.com/nvim-mini/mini.doc .temp/deps/mini.doc; \
	fi

.PHONY: format
format:
	@printf "\033[34mFYLER.NVIM - Code Formatting\033[0m\n"
	@stylua . 2>/dev/null && printf "\033[32mCode formatted\033[0m\n\n" || (printf "\033[31mFormatting failed\033[0m\n\n"; exit 1)

.PHONY: lint
lint:
	@printf "\033[34mFYLER.NVIM - Code Linting\033[0m\n"
	@selene --config selene/config.toml lua 2>/dev/null && printf "\033[32mLinting passed\033[0m\n\n" || (printf "\033[31mLinting failed\033[0m\n\n"; exit 1)

.PHONY: test
test: deps
	@printf "\033[34mFYLER.NVIM - Running Tests\033[0m\n"
	@nvim --headless --clean --noplugin -u tests/minimal_init.lua -l bin/run_tests.lua

.PHONY: test_debug
test_debug:
	@make test DEBUG=1

.PHONY: doc
doc: deps
	@printf "\n\033[34mFYLER.NVIM - Generating vim docs\033[0m\n"
	@nvim --headless --clean --noplugin -l bin/gen_vimdoc.lua


================================================
FILE: README.md
================================================
<div align="center">
  <h1>Fyler.nvim</h1>
  <table>
    <tr>
      <td>
        <strong>A file manager for <a href="https://neovim.io">Neovim</a></strong>
      </td>
    </tr>
  </table>
  <div>
    <img
      alt="License"
      src="https://img.shields.io/github/license/A7Lavinraj/fyler.nvim?style=for-the-badge&logo=starship&color=ee999f&logoColor=D9E0EE&labelColor=302D41"
    />
    <img
      alt="Stars"
      src="https://img.shields.io/github/stars/A7Lavinraj/fyler.nvim?style=for-the-badge&logo=starship&color=c69ff5&logoColor=D9E0EE&labelColor=302D41"
    />
  </div>
</div>

<br>

<div align="center">
  <img
    width="1920"
    height="1080"
    alt="image"
    src="https://github.com/user-attachments/assets/036ebf84-0053-4930-ae91-c0ae95bb417d"
  />
</div>

## Installation

> [!IMPORTANT]
>
> Both **Stable** and **Latest** versions are explained on the
> [WIKI PAGE](https://github.com/A7Lavinraj/fyler.nvim/wiki/installation) in details.

#### Stable

```lua
{
  "A7Lavinraj/fyler.nvim",
  dependencies = { "nvim-mini/mini.icons" },
  branch = "stable",  -- Use stable branch for production
  lazy = false, -- Necessary for `default_explorer` to work properly
  opts = {}
}
```

#### Latest

```lua
{
  "A7Lavinraj/fyler.nvim",
  dependencies = { "nvim-mini/mini.icons" },
  lazy = false, -- Necessary for `default_explorer` to work properly
  opts = {}
}
```

## Usage

You can either open fyler by using the `Fyler` command:

```vim
:Fyler             " Open the finder
:Fyler dir=<cwd>   " Use a different directory path
:Fyler kind=<kind> " Open specified window kind directly

" Map it to a key
nnoremap <leader>e <cmd>Fyler<cr>
```

```lua
-- Or via lua api
vim.keymap.set("n", "<leader>e", "<cmd>Fyler<cr>", { desc = "Open Fyler View" })
```

Or using the lua api:

```lua
local fyler = require('fyler')

-- open using defaults
fyler.open()

-- open as a left most split
fyler.open({ kind = "split_left_most" })

-- open with different directory
fyler.open({ dir = "~" })

-- You can map this to a key
vim.keymap.set("n", "<leader>e", fyler.open, { desc = "Open fyler View" })

-- Wrap in a function to pass additional arguments
vim.keymap.set(
    "n",
    "<leader>e",
    function() fyler.open({ kind = "split_left_most" }) end,
    { desc = "Open Fyler View" }
)
```

> [!NOTE]
> Run `:help fyler.nvim` OR visit [wiki pages](https://github.com/A7Lavinraj/fyler.nvim/wiki) for more detailed explanation and live showcase.

### Credits

- [**GrugFar**](https://github.com/MagicDuck/grug-far.nvim)
- [**Mini.files**](https://github.com/nvim-mini/mini.files)
- [**Neogit**](https://github.com/NeogitOrg/neogit)
- [**Nvim-window-picker**](https://github.com/s1n7ax/nvim-window-picker)
- [**Oil**](https://github.com/stevearc/oil.nvim)
- [**Snacks**](https://github.com/folke/snacks.nvim)
- [**Telescope**](https://github.com/nvim-telescope/telescope.nvim)

---

<h4 align="center">Built with ❤️ for the Neovim community</h4>
<a href="https://github.com/A7Lavinraj/fyler.nvim/graphs/contributors">
  <img
    src="https://contrib.rocks/image?repo=A7Lavinraj/fyler.nvim&max=750&columns=20"
    alt="contributors"
  />
</a>


================================================
FILE: bin/gen_vimdoc.lua
================================================
vim.opt.runtimepath:prepend(vim.fn.getcwd())
vim.opt.runtimepath:prepend(vim.fs.joinpath(vim.fn.getcwd(), ".temp", "deps", "mini.doc"))

local minidoc = require("mini.doc")

minidoc.setup()

minidoc.generate(
  {
    "lua/fyler.lua",
    "lua/fyler/config.lua",
  },
  "doc/fyler.txt",
  {
    hooks = {
      file = function() end,
      sections = {
        ["@signature"] = function(s) s:remove() end,
        ["@return"] = function(s) s.parent:clear_lines() end,
        ["@alias"] = function(s) s.parent:clear_lines() end,
        ["@class"] = function(s) s.parent:clear_lines() end,
        ["@param"] = function(s) s.parent:clear_lines() end,
      },
    },
  }
)


================================================
FILE: bin/run_tests.lua
================================================
require("mini.test").run({
  execute = { stop_on_error = true },
  collect = {
    find_files = function() return vim.fn.globpath("tests", "**/test_*.lua", true, true) end,
    filter_cases = vim.env.FILTER and function(case)
      local desc = vim.deepcopy(case.desc)
      table.remove(desc, 1)
      desc[#desc + 1] = vim.inspect(case.args, { newline = "", indent = "" })
      return table.concat(desc, " "):match(vim.env.FILTER)
    end,
  },
})


================================================
FILE: doc/fyler.txt
================================================
                                                                    *fyler.nvim*
INTRODUCTION

Fyler.nvim is a Neovim file manager plugin based on buffer-based file editing.

Why choose Fyler.nvim over |oil.nvim|?
- Provides a tree view.
- Users can have a full overview of their project without going back and forth
  between directories.

GETTING STARTED

1. Fyler must be setup correctly before use.

USAGE

Fyler can be used through commands or the Lua API.

COMMANDS

:Fyler dir=... kind=...

Parameters:
dir    Path to the directory to open
kind   Display method, one of:
       - `float`
       - `replace`
       - `split_above`
       - `split_above_all`
       - `split_below`
       - `split_below_all`
       - `split_left`
       - `split_left_most`
       - `split_right`
       - `split_right_most`

LUA API

>lua
    local fyler = require("fyler")

    -- Opens finder view with given options
    fyler.open({ dir = "...", kind = "..." })

    -- Toggles finder view with given options
    fyler.toggle({ dir = "...", kind = "..." })

    -- Focuses finder view
    fyler.focus()

    -- Focuses given file path or alternate buffer
    fyler.navigate("...")
<

------------------------------------------------------------------------------
                                                                  *fyler.config*
CONFIGURATION

To setup Fyler put following code anywhere in your neovim runtime:

>lua
  require("fyler").setup()
<

CONFIGURATION.DEFAULTS

To know more about plugin customization. visit:
`https://github.com/A7Lavinraj/fyler.nvim/wiki/configuration`

>lua
  function config.defaults()
    return {
      -- Hooks are functions automatically called for corresponding events:
      -- hooks.on_delete:    function(path: string)
      -- hooks.on_rename:    function(src: string, dst: string)
      -- hooks.on_highlight: function(src: string, dst: string)
      hooks = {},
      -- Integration is a way to hijack generic plugin calls.
      integrations = {
        icon = "mini_icons",
        winpick = "none",
      },
      -- View is a plugin component with dedicated window, UI and config
      views = {
        finder = {
          -- Automatically closes after open a file
          close_on_select = true,
          -- Skip confirmation for simple operations
          confirm_simple = false,
          -- Disables NETRW and take over
          default_explorer = false,
          -- Move to trash instead of permanent delete - MACOS not supported
          delete_to_trash = false,
          -- Define order of information columns
          columns_order = { "link", "permission", "size", "git", "diagnostic" },
          -- Define configuration fo each available information column
          columns = {
            git = {
              enabled = true,
              symbols = {
                Untracked = "?",
                Added = "A",
                Modified = "M",
                Deleted = "D",
                Renamed = "R",
                Copied = "C",
                Conflict = "!",
                Ignored = " ",
              },
            },
            diagnostic = {
              enabled = true,
              symbols = {
                Error = "E",
                Warn = "W",
                Info = "I",
                Hint = "H",
              },
            },
            link = {
              enabled = true,
            },
            permission = {
              enabled = true,
            },
            size = {
              enabled = true,
            },
          },
          -- Overrides directory icons for vairous state
          icon = {
            directory_empty = nil,
            directory_expanded = nil,
            directory_collapsed = nil,
          },
          -- Defines indentation guides config
          indentscope = {
            enabled = true,
            markers = {
              { "│", "FylerIndentMarker" },
              { "└", "FylerIndentMarker" },
            },
          },
          -- Defines key mapping
          mappings = {
            ["q"] = "CloseView",
            ["<CR>"] = "Select",
            ["<C-t>"] = "SelectTab",
            ["|"] = "SelectVSplit",
            ["-"] = "SelectSplit",
            ["^"] = "GotoParent",
            ["="] = "GotoCwd",
            ["."] = "GotoNode",
            ["#"] = "CollapseAll",
            ["<BS>"] = "CollapseNode",
          },
          -- Defines key mapping options
          mappings_opts = {
            nowait = false,
            noremap = true,
            silent = true,
          },
          -- Automatically focus file in the finder UI
          follow_current_file = true,
          -- Automatically updated finder on file system events
          watcher = {
            enabled = false,
          },
          win = {
            border = vim.o.winborder == "" and "single" or vim.o.winborder,
            buf_opts = {
              bufhidden = "hide",
              buflisted = false,
              buftype = "acwrite",
              expandtab = true,
              filetype = "fyler",
              shiftwidth = 2,
              syntax = "fyler",
              swapfile = false,
            },
            kind = "replace",
            kinds = {
              float = {
                height = "70%",
                width = "70%",
                top = "10%",
                left = "15%",
              },
              replace = {},
              split_above = {
                height = "70%",
              },
              split_above_all = {
                height = "70%",
                win_opts = {
                  winfixheight = true,
                },
              },
              split_below = {
                height = "70%",
              },
              split_below_all = {
                height = "70%",
                win_opts = {
                  winfixheight = true,
                },
              },
              split_left = {
                width = "30%",
              },
              split_left_most = {
                width = "30%",
                win_opts = {
                  winfixwidth = true,
                },
              },
              split_right = {
                width = "30%",
              },
              split_right_most = {
                width = "30%",
                win_opts = {
                  winfixwidth = true,
                },
              },
            },
            win_opts = {
              concealcursor = "nvic",
              conceallevel = 3,
              cursorline = false,
              number = false,
              relativenumber = false,
              signcolumn = "no",
              winhighlight = "Normal:FylerNormal,NormalNC:FylerNormalNC",
              wrap = false,
            },
          },
        },
      },
    }
  end

<
------------------------------------------------------------------------------
                                                                   *fyler.setup*
INTEGRATIONS.ICON

Icon provider for file and directory icons.

>lua
  integrations = {
    icon = "mini_icons",        -- nvim-mini/mini.icons (default)
    icon = "nvim_web_devicons", -- nvim-tree/nvim-web-devicons
    icon = "vim_nerdfont",      -- lambdalisue/vim-nerdfont
    icon = "none",              -- disable icons
  }
<

INTEGRATIONS.WINPICK

Window picker for selecting which window to open files in (split kinds).

>lua
  integrations = {
    -- Use winpick = "<provider>" or { provider = "<provider>", opts = {} }
    winpick = "none",
    winpick = "snacks",
    winpick = "builtin",
    winpick = "nvim-window-picker",
    winpick = function(win_filter, on_submit, opts)
      -- custom logic...
    end)
  }
<

Custom winpick function example:

>lua
  integrations = {
    winpick = function(win_filter, on_submit, opts)
      on_submit(require("window-picker").pick_window())
    end,
    opts = {}, -- this is what is passed as opts to the above function
  }
<

 vim:tw=78:ts=8:noet:ft=help:norl:

================================================
FILE: lua/fyler/autocmds.lua
================================================
local M = {}

local augroup = vim.api.nvim_create_augroup("fyler_augroup_global", { clear = true })

function M.setup(config)
  local fyler = require("fyler")
  local helper = require("fyler.views.finder.helper")
  local util = require("fyler.lib.util")
  local Path = require("fyler.lib.path")

  config = config or {}

  if config.values.views.finder.default_explorer then
    -- Disable NETRW plugin
    vim.g.loaded_netrw = 1
    vim.g.loaded_netrwPlugin = 1

    -- Clear NETRW auto commands if NETRW loaded before disable
    vim.cmd("silent! autocmd! FileExplorer *")
    vim.cmd("autocmd VimEnter * ++once silent! autocmd! FileExplorer *")

    vim.api.nvim_create_autocmd("BufEnter", {
      group = augroup,
      pattern = "*",
      desc = "Hijack directory buffers for fyler",
      callback = function(args)
        local bufname = vim.api.nvim_buf_get_name(args.buf)
        if Path.new(bufname):is_directory() or helper.is_protocol_uri(bufname) then
          vim.schedule(function()
            if util.get_buf_option(args.buf, "filetype") == "fyler" then return end

            if vim.api.nvim_buf_is_valid(args.buf) then vim.api.nvim_buf_delete(args.buf, { force = true }) end

            fyler.open({ dir = helper.normalize_uri(bufname) })
          end)
        end
      end,
    })

    vim.api.nvim_create_autocmd({ "BufReadCmd", "SessionLoadPost" }, {
      group = augroup,
      pattern = "fyler://%d+//*",
      desc = "Open fyler protocol URIs",
      callback = function(args)
        local bufname = vim.api.nvim_buf_get_name(args.buf)
        if helper.is_protocol_uri(bufname) then
          local finder_instance = require("fyler.views.finder").instance(bufname)
          if not finder_instance:isopen() then vim.schedule_wrap(fyler.open)({ dir = bufname }) end
        end
      end,
    })
  end

  vim.api.nvim_create_autocmd("ColorScheme", {
    group = augroup,
    desc = "Adjust highlight groups with respect to colorscheme",
    callback = function() require("fyler.lib.hl").setup() end,
  })

  if config.values.views.finder.follow_current_file then
    vim.api.nvim_create_autocmd("BufEnter", {
      group = augroup,
      pattern = "*",
      desc = "Track current focused buffer in finder",
      callback = function(arg)
        if helper.is_protocol_uri(arg.file) or arg.file == "" then return end

        vim.schedule(function()
          if util.get_buf_option(arg.buf, "filetype") ~= "fyler" then fyler.navigate(arg.file) end
        end)
      end,
    })
  end
end

return M


================================================
FILE: lua/fyler/config.lua
================================================
local deprecated = require("fyler.deprecated")
local util = require("fyler.lib.util")

local config = {}

local DEPRECATION_RULES = {
  deprecated.rename("views.finder.git", "views.finder.columns.git"),
  deprecated.transform(
    "views.finder.indentscope.marker",
    "views.finder.indentscope.markers",
    function() return { { "│", "FylerIndentMarker" }, { "└", "FylerIndentMarker" } } end
  ),
}

---@class FylerConfigGitStatus
---@field enabled boolean
---@field symbols table<string, string>

---@alias FylerConfigIntegrationsIcon
---| "none"
---| "mini_icons"
---| "nvim_web_devicons"
---| "vim_nerdfont"

---@alias FylerConfigIntegrationsWinpickName
---| "none"
---| "builtin"
---| "nvim-window-picker"
---| "snacks"

---@alias FylerConfigIntegrationsWinpickFn fun(win_filter: integer[], onsubmit: fun(winid: integer|nil), opts: table)

---@class FylerConfigWinpickBuiltinOpts
---@field chars string|nil

---@class FylerConfigWinpickTable
---@field provider FylerConfigIntegrationsWinpickName|FylerConfigIntegrationsWinpickFn
---@field opts FylerConfigWinpickBuiltinOpts|table<string, any>|nil

---@alias FylerConfigWinpick
---| FylerConfigIntegrationsWinpickName
---| FylerConfigIntegrationsWinpickFn
---| FylerConfigWinpickTable

---@class FylerConfigIntegrations
---@field icon FylerConfigIntegrationsIcon
---@field winpick FylerConfigWinpick

---@alias FylerConfigFinderMapping
---| "CloseView"
---| "GotoCwd"
---| "GotoNode"
---| "GotoParent"
---| "Select"
---| "SelectSplit"
---| "SelectTab"
---| "SelectVSplit"
---| "CollapseAll"
---| "CollapseNode"

---@class FylerConfigIndentScope
---@field enabled boolean
---@field group string
---@field marker string

---@alias FylerConfigBorder
---| "bold"
---| "double"
---| "none"
---| "rounded"
---| "shadow"
---| "single"
---| "solid"

---@class FylerConfigWinKindOptions
---@field height string|number|nil
---@field width string|number|nil
---@field top string|number|nil
---@field left string|number|nil
---@field win_opts table<string, any>|nil

---@class FylerConfigWin
---@field border FylerConfigBorder|string[]
---@field bottom integer|string
---@field buf_opts table<string, any>
---@field footer string
---@field footer_pos string
---@field height integer|string
---@field kind WinKind
---@field kinds table<WinKind|string, FylerConfigWinKindOptions>
---@field left integer|string
---@field right integer|string
---@field title_pos string
---@field top integer|string
---@field width integer|string
---@field win_opts table<string, any>

---@class FylerConfigViewsFinder
---@field close_on_select boolean
---@field confirm_simple boolean
---@field default_explorer boolean
---@field delete_to_trash boolean
---@field git_status FylerConfigGitStatus
---@field icon table<string, string|nil>
---@field indentscope FylerConfigIndentScope
---@field mappings table<string, FylerConfigFinderMapping|function>
---@field mappings_opts vim.keymap.set.Opts
---@field follow_current_file boolean
---@field win FylerConfigWin

---@class FylerConfigViews
---@field finder FylerConfigViewsFinder

---@class FylerConfig
---@field hooks table<string, any>
---@field integrations FylerConfigIntegrations
---@field views FylerConfigViews

---@class FylerSetupIntegrations
---@field icon FylerConfigIntegrationsIcon|nil
---@field winpick FylerConfigWinpick|nil

---@class FylerSetupIndentScope
---@field enabled boolean|nil
---@field group string|nil
---@field marker string|nil

---@class FylerSetupWin
---@field border FylerConfigBorder|string[]|nil
---@field buf_opts table<string, any>|nil
---@field kind WinKind|nil
---@field kinds table<WinKind|string, FylerConfigWinKindOptions>|nil
---@field win_opts table<string, any>|nil

---@class FylerSetup
---@field hooks table<string, any>|nil
---@field integrations FylerSetupIntegrations|nil
---@field views FylerConfigViews|nil

--- CONFIGURATION
---
--- To setup Fyler put following code anywhere in your neovim runtime:
---
--- >lua
---   require("fyler").setup()
--- <
---
--- CONFIGURATION.DEFAULTS
---
--- To know more about plugin customization. visit:
--- `https://github.com/A7Lavinraj/fyler.nvim/wiki/configuration`
---
---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
---@tag fyler.config
function config.defaults()
  return {
    -- Hooks are functions automatically called for corresponding events:
    -- hooks.on_delete:    function(path: string)
    -- hooks.on_rename:    function(src: string, dst: string)
    -- hooks.on_highlight: function(src: string, dst: string)
    hooks = {},
    -- Integration is a way to hijack generic plugin calls.
    integrations = {
      icon = "mini_icons",
      winpick = "none",
    },
    -- View is a plugin component with dedicated window, UI and config
    views = {
      finder = {
        -- Automatically closes after open a file
        close_on_select = true,
        -- Skip confirmation for simple operations
        confirm_simple = false,
        -- Disables NETRW and take over
        default_explorer = false,
        -- Move to trash instead of permanent delete
        delete_to_trash = false,
        -- Define order of information columns
        columns_order = { "link", "permission", "size", "git", "diagnostic" },
        -- Define configuration fo each available information column
        columns = {
          git = {
            enabled = true,
            symbols = {
              Untracked = "?",
              Added = "A",
              Modified = "M",
              Deleted = "D",
              Renamed = "R",
              Copied = "C",
              Conflict = "!",
              Ignored = " ",
            },
          },
          diagnostic = {
            enabled = true,
            symbols = {
              Error = "E",
              Warn = "W",
              Info = "I",
              Hint = "H",
            },
          },
          link = {
            enabled = true,
          },
          permission = {
            enabled = true,
          },
          size = {
            enabled = true,
          },
        },
        -- Overrides directory icons for vairous state
        icon = {
          directory_empty = nil,
          directory_expanded = nil,
          directory_collapsed = nil,
        },
        -- Defines indentation guides config
        indentscope = {
          enabled = true,
          markers = {
            { "│", "FylerIndentMarker" },
            { "└", "FylerIndentMarker" },
          },
        },
        -- Defines key mapping
        mappings = {
          ["q"] = "CloseView",
          ["<CR>"] = "Select",
          ["<C-t>"] = "SelectTab",
          ["|"] = "SelectVSplit",
          ["-"] = "SelectSplit",
          ["^"] = "GotoParent",
          ["="] = "GotoCwd",
          ["."] = "GotoNode",
          ["#"] = "CollapseAll",
          ["<BS>"] = "CollapseNode",
        },
        -- Defines key mapping options
        mappings_opts = {
          nowait = false,
          noremap = true,
          silent = true,
        },
        -- Automatically focus file in the finder UI
        follow_current_file = true,
        -- Automatically updated finder on file system events
        watcher = {
          enabled = false,
        },
        win = {
          border = vim.o.winborder == "" and "single" or vim.o.winborder,
          buf_opts = {
            bufhidden = "hide",
            buflisted = false,
            buftype = "acwrite",
            expandtab = true,
            filetype = "fyler",
            shiftwidth = 2,
            syntax = "fyler",
            swapfile = false,
          },
          kind = "replace",
          kinds = {
            float = {
              height = "70%",
              width = "70%",
              top = "10%",
              left = "15%",
            },
            replace = {},
            split_above = {
              height = "70%",
            },
            split_above_all = {
              height = "70%",
              win_opts = {
                winfixheight = true,
              },
            },
            split_below = {
              height = "70%",
            },
            split_below_all = {
              height = "70%",
              win_opts = {
                winfixheight = true,
              },
            },
            split_left = {
              width = "30%",
            },
            split_left_most = {
              width = "30%",
              win_opts = {
                winfixwidth = true,
              },
            },
            split_right = {
              width = "30%",
            },
            split_right_most = {
              width = "30%",
              win_opts = {
                winfixwidth = true,
              },
            },
          },
          win_opts = {
            concealcursor = "nvic",
            conceallevel = 3,
            cursorline = false,
            number = false,
            relativenumber = false,
            signcolumn = "no",
            winhighlight = "Normal:FylerNormal,NormalNC:FylerNormalNC",
            wrap = false,
          },
        },
      },
    },
  }
end

---@param name string
---@param kind WinKind|nil
---@return FylerConfigViewsFinder
function config.view_cfg(name, kind)
  local view = vim.deepcopy(config.values.views[name] or {})
  view.win = require("fyler.lib.util").tbl_merge_force(view.win, view.win.kinds[kind or view.win.kind])
  return view
end

---@param name string
---@return table<string, string[]>
function config.rev_maps(name)
  local rev_maps = {}
  for k, v in pairs(config.values.views[name].mappings or {}) do
    if type(v) == "string" then
      local current = rev_maps[v]
      if current then
        table.insert(current, k)
      else
        rev_maps[v] = { k }
      end
    end
  end

  setmetatable(rev_maps, {
    __index = function() return "<nop>" end,
  })

  return rev_maps
end

---@param name string
---@return table<string, function>
function config.usr_maps(name)
  local user_maps = {}
  for k, v in pairs(config.values.views[name].mappings or {}) do
    if type(v) == "function" then user_maps[k] = v end
  end

  return user_maps
end

--- INTEGRATIONS.ICON
---
--- Icon provider for file and directory icons.
---
--- >lua
---   integrations = {
---     icon = "mini_icons",        -- nvim-mini/mini.icons (default)
---     icon = "nvim_web_devicons", -- nvim-tree/nvim-web-devicons
---     icon = "vim_nerdfont",      -- lambdalisue/vim-nerdfont
---     icon = "none",              -- disable icons
---   }
--- <
---
--- INTEGRATIONS.WINPICK
---
--- Window picker for selecting which window to open files in (split kinds).
---
--- >lua
---   integrations = {
---     -- Use winpick = "<provider>" or { provider = "<provider>", opts = {} }
---     winpick = "none",
---     winpick = "snacks",
---     winpick = "builtin",
---     winpick = "nvim-window-picker",
---     winpick = function(win_filter, on_submit, opts)
---       -- custom logic...
---     end)
---   }
--- <
---
--- Custom winpick function example:
---
--- >lua
---   integrations = {
---     winpick = function(win_filter, on_submit, opts)
---       on_submit(require("window-picker").pick_window())
---     end,
---     opts = {}, -- this is what is passed as opts to the above function
---   }
--- <
---
---@tag fyler.setup

---@param opts FylerSetup|nil
function config.setup(opts)
  opts = opts or {}

  config.values = util.tbl_merge_force(config.defaults(), deprecated.migrate(opts, DEPRECATION_RULES))

  local icon_provider = config.values.integrations.icon
  if type(icon_provider) == "string" then
    config.icon_provider = require("fyler.integrations.icon")[icon_provider]
  else
    config.icon_provider = icon_provider
  end

  -- Support shorthand: winpick = "provider-name" or winpick = function
  local winpick_config = config.values.integrations.winpick
  local winpick_provider = type(winpick_config) == "table" and winpick_config.provider or winpick_config
  config.winpick_opts = type(winpick_config) == "table" and winpick_config.opts or {}
  if type(winpick_provider) == "string" then
    config.winpick_provider = require("fyler.integrations.winpick")[winpick_provider]
  else
    config.winpick_provider = winpick_provider
  end

  for _, sub_module in ipairs({
    "fyler.autocmds",
    "fyler.hooks",
    "fyler.lib.hl",
  }) do
    require(sub_module).setup(config)
  end
end

return config


================================================
FILE: lua/fyler/deprecated.lua
================================================
local M = {}

local warnings = {}

local function split_path(path)
  local parts = {}
  for part in path:gmatch("[^.]+") do
    table.insert(parts, part)
  end
  return parts
end

local function get_nested(tbl, path)
  local parts = split_path(path)
  local current = tbl

  for _, part in ipairs(parts) do
    if type(current) ~= "table" then return nil end
    current = current[part]
    if current == nil then return nil end
  end

  return current
end

local function set_nested(tbl, path, value)
  local parts = split_path(path)
  local current = tbl

  for i = 1, #parts - 1 do
    local part = parts[i]
    if type(current[part]) ~= "table" then current[part] = {} end
    current = current[part]
  end

  current[parts[#parts]] = value
end

local function delete_nested(tbl, path)
  local parts = split_path(path)
  local current = tbl

  for i = 1, #parts - 1 do
    local part = parts[i]
    if type(current) ~= "table" or current[part] == nil then return end
    current = current[part]
  end

  current[parts[#parts]] = nil
end

local function path_exists(tbl, path) return get_nested(tbl, path) ~= nil end

local function format_warning(warning)
  local lines = {}
  local rule = warning.rule

  table.insert(lines, string.format("Deprecated configuration detected: '%s'", warning.path))

  if rule.message then table.insert(lines, "  " .. rule.message) end

  if rule.version then table.insert(lines, string.format("  Deprecated in: v%s", rule.version)) end

  if rule.removal_version then table.insert(lines, string.format("  Will be removed in: v%s", rule.removal_version)) end

  if rule.to then
    table.insert(lines, string.format("  Use '%s' instead", rule.to))
  else
    table.insert(lines, "  This option has been removed")
  end

  table.insert(lines, "  " .. warning.suggestion)

  return table.concat(lines, "\n")
end

local function show_warnings()
  if #warnings == 0 then return end

  local message_parts = {
    "Fyler: Deprecated configuration options detected",
    string.rep("=", 60),
  }

  for _, warning in ipairs(warnings) do
    table.insert(message_parts, "")
    table.insert(message_parts, format_warning(warning))
  end

  table.insert(message_parts, "")
  table.insert(message_parts, string.rep("=", 60))
  table.insert(message_parts, "Please update your configuration to avoid future issues.")
  table.insert(message_parts, "See :h Fyler.Config for current options.")

  local full_message = table.concat(message_parts, "\n")

  vim.notify(full_message, vim.log.levels.WARN)
end

local function apply_rule(config, rule)
  if not path_exists(config, rule.from) then return false end

  local old_value = get_nested(config, rule.from)

  local new_value = old_value
  if rule.transform then new_value = rule.transform(old_value, config) end

  if rule.to then set_nested(config, rule.to, new_value) end

  delete_nested(config, rule.from)

  local suggestion
  if rule.to then
    if rule.transform then
      suggestion = string.format("Update your config: %s = <transformed_value>", rule.to)
    else
      suggestion = string.format("Update your config: %s = %s", rule.to, vim.inspect(old_value))
    end
  else
    suggestion = string.format("Remove '%s' from your configuration", rule.from)
  end

  table.insert(warnings, {
    path = rule.from,
    rule = rule,
    suggestion = suggestion,
  })

  return true
end

function M.migrate(user_config, rules)
  warnings = {}

  local config = vim.deepcopy(user_config)

  for _, rule in ipairs(rules or {}) do
    apply_rule(config, rule)
  end

  if #warnings > 0 then show_warnings() end

  return config
end

function M.rename(from, to, opts)
  opts = opts or {}
  return vim.tbl_extend("force", {
    from = from,
    to = to,
    transform = nil,
  }, opts)
end

function M.remove(from, opts)
  opts = opts or {}
  return vim.tbl_extend("force", {
    from = from,
    to = nil,
    transform = nil,
  }, opts)
end

function M.transform(from, to, transform, opts)
  opts = opts or {}
  return vim.tbl_extend("force", {
    from = from,
    to = to,
    transform = transform,
  }, opts)
end

return M


================================================
FILE: lua/fyler/hooks.lua
================================================
local M = {}
local hooks = {}

-- Get attached active LSP clients
local function get_active_lsp_clients()
  if vim.lsp.get_clients then
    return vim.lsp.get_clients()
  else
    ---@diagnostic disable-next-line: deprecated
    return vim.lsp.get_active_clients()
  end
end

hooks.on_highlight = function() end

---@param path string
function hooks.on_delete(path)
  if not path then return end

  local path_bufnr = vim.fn.bufnr(path)
  if path_bufnr == -1 then return end
  path_bufnr = path_bufnr == 0 and vim.api.nvim_get_current_buf() or path_bufnr

  vim.api.nvim_buf_call(path_bufnr, function()
    for _, winid in ipairs(vim.fn.win_findbuf(path_bufnr)) do
      vim.api.nvim_win_call(winid, function()
        if not vim.api.nvim_win_is_valid(winid) or vim.api.nvim_win_get_buf(winid) ~= path_bufnr then return end

        local alternate_bufnr = vim.fn.bufnr("#")
        if alternate_bufnr ~= path_bufnr and vim.fn.buflisted(alternate_bufnr) == 1 then
          return vim.api.nvim_win_set_buf(winid, alternate_bufnr)
        end

        ---@diagnostic disable-next-line: param-type-mismatch
        local has_previous = pcall(vim.cmd, "bprevious")
        if has_previous and path_bufnr ~= vim.api.nvim_win_get_buf(winid) then return end

        local new_bufnr = vim.api.nvim_create_buf(true, false)
        vim.api.nvim_win_set_buf(winid, new_bufnr)
      end)
    end

    if vim.api.nvim_buf_is_valid(path_bufnr) then
      ---@diagnostic disable-next-line: param-type-mismatch
      pcall(vim.cmd, "bdelete! " .. path_bufnr)
    end
  end)
end

---@param src string
---@param dst string
function hooks.on_rename(src, dst)
  if not src then return end
  if not dst then return end

  local changes = {
    files = {
      {
        oldUri = vim.uri_from_fname(src),
        newUri = vim.uri_from_fname(dst),
      },
    },
  }

  local clients = get_active_lsp_clients()
  for _, client in ipairs(clients) do
    if client:supports_method("workspace/willRenameFiles") then
      local response = client:request_sync("workspace/willRenameFiles", changes, 1000, 0)
      if response and response.result ~= nil then
        vim.lsp.util.apply_workspace_edit(response.result, client.offset_encoding)
      end
    end
  end

  local src_bufnr = vim.fn.bufnr(src)
  if src_bufnr >= 0 then
    local dst_bufnr = vim.fn.bufadd(dst)
    require("fyler.lib.util").set_buf_option(dst_bufnr, "buflisted", true)

    for _, winid in ipairs(vim.fn.win_findbuf(src_bufnr)) do
      vim.api.nvim_win_call(winid, function() vim.cmd("buffer " .. dst_bufnr) end)
    end

    vim.api.nvim_buf_delete(src_bufnr, { force = true })
  end

  for _, client in ipairs(clients) do
    if client:supports_method("workspace/didRenameFiles") then client:notify("workspace/didRenameFiles", changes) end
  end
end

function M.setup(config)
  for name, fn in pairs(hooks) do
    M[name] = config.values.hooks[name] or fn
  end
end

return M


================================================
FILE: lua/fyler/input.lua
================================================
local M = setmetatable({}, {
  __index = function(_, key)
    local ok, input = pcall(require, "fyler.inputs." .. key)
    assert(ok, string.format("Input '%s' not found", key))
    return input
  end,
})

return M


================================================
FILE: lua/fyler/inputs/confirm.lua
================================================
local Ui = require("fyler.lib.ui")
local Win = require("fyler.lib.win")
local util = require("fyler.lib.util")

local Confirm = {}
Confirm.__index = Confirm

local function resolve_dim(width, height)
  width = math.max(25, math.min(vim.o.columns, width)) + 2
  height = math.max(1, math.min(16, height))
  local left = math.floor((vim.o.columns - width) * 0.5)
  local top = math.floor((vim.o.lines - height) * 0.5)
  return math.floor(width), math.floor(height), left, top
end

function Confirm:open(options, message, onsubmit)
  local width, height, left, top = resolve_dim(options.width, options.height)
  -- stylua: ignore start
  self.window = Win.new {
    kind       = "float",
    enter      = true,
    width      = width,
    height     = height,
    left       = left,
    top        = top,
    border     = vim.o.winborder == "" and "rounded" or vim.o.winborder,
    footer     = " Want to continue? (y|n) ",
    footer_pos = "center",
    buf_opts   = { modifiable = false },
    win_opts   = { winhighlight = "Normal:FylerNormal,NormalNC:FylerNormalNC" },
    mappings   = {
      [{ 'y', 'o', '<Enter>' }] = function()
        self.window:hide()
        pcall(onsubmit, true)
      end,
      [{ 'n', 'c', '<ESC>' }] = function()
        self.window:hide()
        pcall(onsubmit, false)
      end
    },
    autocmds   = {
      QuitPre = function()
        local cmd = util.cmd_history()
        self.window:hide()
        pcall(onsubmit)
        if cmd == "qa" or cmd == "qall" or cmd == "quitall" then
          vim.schedule(vim.cmd.quitall)
        end
      end
    },
    render     = function()
      if type(message) == "table" and type(message[1]) == "string" then
        ---@diagnostic disable-next-line: param-type-mismatch
        self.window.ui:render(Ui.Column(util.tbl_map(message, Ui.Text)))
      else
        self.window.ui:render(message)
      end
    end
  }
  -- stylua: ignore end

  self.window:show()
end

local M = {}

function M.open(message, on_submit)
  local width, height = 0, 0
  if message.width then
    width, height = message:width(), message:height()
  else
    height = #message
    for _, row in pairs(message) do
      width = math.max(width, #row)
    end
  end

  setmetatable({}, Confirm):open({
    width = width,
    height = height,
  }, message, on_submit)
end

return M


================================================
FILE: lua/fyler/inputs/winpick.lua
================================================
local Ui = require("fyler.lib.ui")
local Win = require("fyler.lib.win")
local util = require("fyler.lib.util")

local M = {}

---@param win_filter integer[]
---@param onsubmit fun(winid: integer|nil)
---@param opts FylerConfigWinpickBuiltinOpts|nil
function M.open(win_filter, onsubmit, opts)
  opts = opts or {}
  local chars = opts.chars or "asdfghjkl;"

  local winids = util.tbl_filter(vim.api.nvim_tabpage_list_wins(0), function(win)
    return not util.if_any(win_filter, function(c) return c == win end)
  end)
  assert(string.len(chars) >= #winids, "too many windows to select")

  if #winids <= 1 then return onsubmit(winids[1]) end

  local winid_to_win = {}
  local char_to_winid = {}
  for i, winid in ipairs(winids) do
    winid_to_win[winid] = Win.new({
      buf_opts = { modifiable = false },
      enter = false,
      height = 1,
      kind = "float",
      left = 0,
      top = 0,
      width = 3,
      win = winid,
    })
    winid_to_win[winid].render = function()
      winid_to_win[winid].ui:render({
        children = {
          Ui.Text(string.format(" %s ", string.sub(chars, i, i)), { highlight = "FylerWinPick" }),
        },
      }, function()
        if i == #winids then
          vim.cmd([[ redraw! ]])

          local winid = char_to_winid[vim.fn.getcharstr()]
          for _, win in pairs(winid_to_win) do
            win:hide()
          end

          onsubmit(winid)
        end
      end)
    end
    char_to_winid[string.sub(chars, i, i)] = winid
    winid_to_win[winid]:show()
  end
end

return M


================================================
FILE: lua/fyler/integrations/icon/init.lua
================================================
---@class IconIntegration
---@field mini_icon MiniIconsIntegration
---@field nvim_web_devicons NvimWebDeviconsIntegration
---@field vim_nerdfont VimNerdfontIntegration
local M = {}

setmetatable(M, {
  __index = function(_, k)
    if k == "none" then
      return function() end
    end

    local icon_provider = require("fyler.integrations.icon." .. k)

    return function(type, path) return icon_provider.get(type, path) end
  end,
})

return M


================================================
FILE: lua/fyler/integrations/icon/mini_icons.lua
================================================
---@class MiniIconsIntegration
local M = {}

function M.get(type, name)
  local ok, miniicons = pcall(require, "mini.icons")
  assert(ok, "mini.icons are not installed or not loaded")

  local supported = {
    default = true,
    directory = true,
    extension = true,
    file = true,
    filetype = true,
    lsp = true,
    os = true,
  }

  local category = supported[type] and type or "file"
  return miniicons.get(category, name)
end

return M


================================================
FILE: lua/fyler/integrations/icon/nvim_web_devicons.lua
================================================
---@class NvimWebDeviconsIntegration
local M = {}

function M.get(type, path)
  local ok, devicons = pcall(require, "nvim-web-devicons")
  assert(ok, "nvim-web-devicons are not installed or not loaded")

  local icon, hl = devicons.get_icon(vim.fn.fnamemodify(path, ":t"))
  icon = (type == "directory" and "" or (icon or ""))
  hl = hl or (type == "directory" and "Fylerblue" or "")

  return icon, hl
end

return M


================================================
FILE: lua/fyler/integrations/icon/vim_nerdfont.lua
================================================
---@class VimNerdfontIntegration
local M = {}

function M.get(type, path)
  assert(vim.fn.exists("*nerdfont#find"), "vim-nerdfont are not installed or not loaded")

  if type == "directory" then
    return vim.fn["nerdfont#directory#find"]()
  else
    return vim.fn["nerdfont#find"](path)
  end
end

return M


================================================
FILE: lua/fyler/integrations/winpick/init.lua
================================================
---@class WinpickIntegration
---@field none fun(win_filter: integer[], onsubmit: fun(winid: integer|nil), opts: table|nil)
---@field builtin fun(win_filter: integer[], onsubmit: fun(winid: integer|nil), opts: table|nil)
---@field nvim_window_picker fun(win_filter: integer[], onsubmit: fun(winid: integer|nil), opts: table|nil)
---@field snacks fun(win_filter: integer[], onsubmit: fun(winid: integer|nil), opts: table|nil)
local M = {}

setmetatable(M, {
  __index = function(_, k)
    if k == "none" then
      return function(win_filter, onsubmit, _)
        local prev_winnr = vim.fn.winnr("#")
        local prev_winid = prev_winnr ~= 0 and vim.fn.win_getid(prev_winnr) or nil
        if prev_winid and vim.tbl_contains(win_filter, prev_winid) then prev_winid = nil end
        onsubmit(prev_winid)
      end
    end

    if k == "builtin" then return require("fyler.inputs.winpick").open end

    local ok, winpick_provider = pcall(require, "fyler.integrations.winpick." .. k:gsub("-", "_"))
    assert(ok, string.format("Winpick integration '%s' not found", k))

    return function(win_filter, onsubmit, opts) return winpick_provider.open(win_filter, onsubmit, opts) end
  end,
})

return M


================================================
FILE: lua/fyler/integrations/winpick/nvim_window_picker.lua
================================================
---@class NvimWindowPickerIntegration
local M = {}

--- Note: win_filter is unused here because we filter by filetype instead,
--- which preserves the user's nvim-window-picker filter_rules configuration.
---@param _ integer[] Window IDs to filter (unused, filtered by filetype)
---@param onsubmit fun(winid: integer|nil)
---@param opts table<string, any>|nil Options passed to nvim-window-picker's pick_window()
function M.open(_, onsubmit, opts)
  local ok, window_picker = pcall(require, "window-picker")
  assert(ok, "nvim-window-picker is not installed or not loaded")

  opts = opts or {}

  -- Merge "fyler" into filter_rules.bo.filetype to exclude fyler windows
  local user_filetypes = opts.filter_rules and opts.filter_rules.bo and opts.filter_rules.bo.filetype or {}
  local filetypes = vim.list_extend({ "fyler" }, user_filetypes)

  local picker_opts = vim.tbl_deep_extend("force", opts, {
    filter_rules = {
      bo = {
        filetype = filetypes,
      },
    },
  })

  local winid = window_picker.pick_window(picker_opts)

  onsubmit(winid)
end

return M


================================================
FILE: lua/fyler/integrations/winpick/snacks.lua
================================================
---@class SnacksWinpickIntegration
local M = {}

--- Note: win_filter is unused here because snacks.picker.util.pick_win
--- filters by filetype instead.
---@param _ integer[] Window IDs to filter (unused, filtered by filetype)
---@param onsubmit fun(winid: integer|nil)
---@param opts table<string, any>|nil Options passed to snacks.picker.util.pick_win()
function M.open(_, onsubmit, opts)
  local ok, snacks_picker_util = pcall(require, "snacks.picker.util")
  assert(ok, "snacks.nvim picker is not installed or not loaded")

  opts = opts or {}

  -- Merge filter to exclude fyler windows
  local user_filter = opts.filter
  local picker_opts = vim.tbl_deep_extend("force", opts, {
    filter = function(win, buf)
      if vim.bo[buf].filetype == "fyler" then return false end
      if user_filter then return user_filter(win, buf) end
      return true
    end,
  })

  local winid = snacks_picker_util.pick_win(picker_opts)

  onsubmit(winid)
end

return M


================================================
FILE: lua/fyler/lib/async.lua
================================================
local log = require("fyler.log")
local util = require("fyler.lib.util")

local M = {}

local function trace_error(message, co)
  local trace = debug.traceback(co or nil)
  local full_error = string.format("%s\n%s", message, trace)
  log.error(full_error)
  return full_error
end

local function execute_async(async_fn, next, ...)
  local co = coroutine.create(async_fn)
  local args = { ... }

  local function step(...)
    local success, result = coroutine.resume(co, ...)

    if not success then
      trace_error("Coroutine error: " .. tostring(result), co)
      if next then next(nil, result) end
      return
    end

    local status = coroutine.status(co)

    if status == "dead" then
      if next then next(result) end
    elseif status == "suspended" then
      if type(result) == "function" then
        local exec_success, exec_error = pcall(result, step)
        if not exec_success then
          trace_error("Error executing yielded function: " .. tostring(exec_error), co)
          if next then next(nil, exec_error) end
        end
      else
        local error_msg = "Invalid yield: expected function, got " .. type(result)
        trace_error(error_msg, co)
        if next then next(nil, error_msg) end
      end
    end
  end

  local start_success, start_error = pcall(step, util.unpack(args))
  if not start_success then
    trace_error("Failed to start execution: " .. tostring(start_error))
    if next then next(nil, start_error) end
  end
end

function M.await(fn, ...)
  local args = { ... }

  return coroutine.yield(function(resume_fn)
    table.insert(args, function(...)
      local success, error = pcall(resume_fn, ...)
      if not success then trace_error("Error in await callback: " .. tostring(error)) end
    end)

    local success, error = pcall(fn, util.unpack(args))
    if not success then
      trace_error("Error calling awaited function: " .. tostring(error))
      resume_fn(nil, error)
    end
  end)
end

function M.wrap(fn)
  return function(...)
    local args = { ... }

    return M.await(function(cb)
      table.insert(args, cb)
      execute_async(fn, nil, util.unpack(args))
    end)
  end
end

function M.void_wrap(async_fn)
  return function(...) execute_async(async_fn, nil, ...) end
end

function M.void(async_fn, cb) execute_async(async_fn, cb) end

M.schedule = M.wrap(vim.schedule)

setmetatable(M, {
  __index = function(_, k)
    local _, module = pcall(require, "fyler.lib.async." .. k)
    if not module then
      require("fyler.log").error(string.format("Module '%s' is not implemented", k))
    else
      return module
    end
  end,
})

return M


================================================
FILE: lua/fyler/lib/diagnostic.lua
================================================
local config = require("fyler.config")
local util = require("fyler.lib.util")

local M = {}

local severity_names = {
  [vim.diagnostic.severity.ERROR] = "Error",
  [vim.diagnostic.severity.WARN] = "Warn",
  [vim.diagnostic.severity.INFO] = "Info",
  [vim.diagnostic.severity.HINT] = "Hint",
}

local severity_hl = {
  [vim.diagnostic.severity.ERROR] = "FylerDiagnosticError",
  [vim.diagnostic.severity.WARN] = "FylerDiagnosticWarn",
  [vim.diagnostic.severity.INFO] = "FylerDiagnosticInfo",
  [vim.diagnostic.severity.HINT] = "FylerDiagnosticHint",
}

local function count_diagnostics_by_path()
  local lookup = {}

  if not vim.diagnostic then return lookup end

  for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do
    local name = vim.api.nvim_buf_get_name(bufnr)
    if name ~= "" then
      name = vim.fs.normalize(name)

      local diagnostics = vim.diagnostic.get(bufnr)
      if diagnostics and #diagnostics > 0 then
        local counts = {}
        local highest_severity = nil

        for _, diag in ipairs(diagnostics) do
          local sev = diag.severity
          if sev then
            counts[sev] = (counts[sev] or 0) + 1
            highest_severity = highest_severity and math.min(highest_severity, sev) or sev
          end
        end

        lookup[name] = {
          counts = counts,
          highest_severity = highest_severity,
        }
      end
    end
  end

  return lookup
end

function M.map_entries(_, entries)
  local diag_by_path = count_diagnostics_by_path()
  local symbols = (config.values.views.finder.columns.diagnostic or {}).symbols or {}

  return util.tbl_map(entries, function(path)
    local normalized_path = vim.fs.normalize(path)

    local info = diag_by_path[normalized_path]
    if not info or not info.highest_severity then return { "", nil } end

    local sev = info.highest_severity
    local sev_name = severity_names[sev]
    local sev_symbol = sev_name and symbols[sev_name] or ""
    local count = info.counts[sev] or 0
    if count == 0 then return { "", nil } end

    -- local text = sev_symbol .. count
    local text = sev_symbol
    local hl = severity_hl[sev]

    return {
      text,
      hl,
    }
  end)
end

function M.map_entries_async(root_dir, entries, onmapped)
  vim.schedule(function() onmapped(M.map_entries(root_dir, entries)) end)
end

return M


================================================
FILE: lua/fyler/lib/fs.lua
================================================
local Path = require("fyler.lib.path")
local hooks = require("fyler.hooks")
local util = require("fyler.lib.util")

local M = {}

function M.write(opts, _next)
  local path = Path.new(opts.path):os_path()
  local data = opts.data or {}

  M.mkdir({
    path = Path.new(path):parent():os_path(),
    flags = { p = true },
  }, function(err)
    if err then
      pcall(_next, err)
      return
    end

    vim.uv.fs_open(path, "w", 420, function(err_open, fd)
      if err_open or not fd then
        pcall(_next, err_open)
        return
      end

      vim.uv.fs_write(fd, data, -1, function(err_write, bytes)
        if not bytes then
          vim.uv.fs_close(fd, function()
            M.rm({
              path = path,
            }, function() pcall(_next, string.format("Failed to write to %s: %s", path, err_write)) end)
          end)
        else
          vim.uv.fs_close(fd, function(err_close) pcall(_next, err_close) end)
        end
      end)
    end)
  end)
end

function M.ls(opts, _next)
  local path = Path.new(opts.path):os_path()

  vim.uv.fs_opendir(path, function(err_open, dir)
    if err_open or not dir then
      pcall(_next, err_open, nil)
      return
    end

    local contents = {}
    -- NOTE: Polling is necessary because `fs_readdir: async_version` list
    -- contents in chunks
    local function poll_entries()
      vim.uv.fs_readdir(dir, function(err_read, entries)
        if err_read then
          vim.uv.fs_closedir(dir, function() pcall(_next, err_read, nil) end)
          return
        end

        if entries and #entries > 0 then
          vim.list_extend(
            contents,
            util.tbl_map(entries, function(e)
              local entry_path_obj = Path.new(path):join(e.name)
              local entry_path, entry_type = entry_path_obj:res_link()
              if e.type == "link" then
                return {
                  name = e.name,
                  path = entry_path,
                  type = entry_type or "file",
                  link = entry_path_obj:posix_path(),
                }
              else
                return {
                  name = e.name,
                  type = e.type,
                  path = entry_path_obj:posix_path(),
                }
              end
            end)
          )

          poll_entries() -- Continue reading
        else
          vim.uv.fs_closedir(dir, function() pcall(_next, nil, contents) end)
        end
      end)
    end

    poll_entries()
  end, 1000)
end

function M.touch(opts, _next)
  local path = Path.new(opts.path):os_path()

  vim.uv.fs_open(path, "a", 420, function(err_open, fd)
    if err_open or not fd then
      pcall(_next, err_open)
      return
    end

    vim.uv.fs_close(fd, function(err_close) pcall(_next, err_close) end)
  end)
end

function M.mkdir(opts, _next)
  local flags = opts.flags or {}

  if flags.p then
    local prefixes = {}
    for _, prefix in Path.new(opts.path):iter() do
      table.insert(prefixes, prefix)
    end

    local function create_next(index)
      if index > #prefixes then return pcall(_next) end

      if Path.new(prefixes[index]):exists() then
        create_next(index + 1)
      else
        M.mkdir({ path = prefixes[index] }, function() create_next(index + 1) end)
      end
    end

    create_next(1)
  else
    vim.uv.fs_mkdir(Path.new(opts.path):os_path(), 493, function(err) pcall(_next, err) end)
  end
end

local function _read_dir_iter(opts, _next)
  local path = Path.new(opts.path):os_path()

  vim.uv.fs_opendir(path, function(err_open, dir)
    if err_open or not dir then
      pcall(_next, nil, function() end)
      return
    end

    vim.uv.fs_readdir(dir, function(err_read, entries)
      vim.uv.fs_closedir(dir, function()
        if err_read or not entries then
          pcall(_next, nil, function() end)
        else
          local i = 0
          pcall(_next, nil, function()
            i = i + 1
            if i <= #entries then return i, entries[i] end
          end)
        end
      end)
    end)
  end, 1000)
end

function M.rm(opts, _next)
  local path = Path.new(opts.path):os_path()
  local flags = opts.flags or {}

  flags = flags or {}

  if Path.new(path):is_directory() then
    assert(flags.r, "cannot remove directory without -r flag: " .. path)

    _read_dir_iter({
      path = path,
    }, function(err, iter)
      if err then
        pcall(_next, err)
        return
      end

      local entries = {}
      for _, e in iter do
        table.insert(entries, e)
      end

      local function remove_next(index)
        if index > #entries then
          vim.uv.fs_rmdir(path, function(err_rmdir) pcall(_next, err_rmdir) end)
          return
        end

        M.rm({
          path = Path.new(path):join(entries[index].name):os_path(),
          flags = flags,
        }, function(err)
          if err then
            pcall(_next, err)
            return
          end
          remove_next(index + 1)
        end)
      end

      remove_next(1)
    end)
  else
    vim.uv.fs_unlink(path, function(err) pcall(_next, err) end)
  end
end

function M.mv(opts, _next)
  local src = Path.new(opts.src):os_path()
  local dst = Path.new(opts.dst):os_path()

  M.mkdir({
    path = Path.new(dst):parent():os_path(),
    flags = { p = true },
  }, function()
    if Path.new(src):is_directory() then
      M.mkdir({
        path = dst,
        flags = { p = true },
      }, function()
        _read_dir_iter({
          path = src,
        }, function(err_iter, iter)
          if err_iter then
            pcall(_next, err_iter)
            return
          end

          local entries = {}
          for _, e in iter do
            table.insert(entries, e)
          end

          local function move_next(index)
            if index > #entries then
              vim.uv.fs_rmdir(src, function(err_rmdir) pcall(_next, err_rmdir) end)
              return
            end

            M.mv({
              src = Path.new(src):join(entries[index].name):os_path(),
              dst = Path.new(dst):join(entries[index].name):os_path(),
            }, function(err)
              if err then
                pcall(_next, err)
              else
                move_next(index + 1)
              end
            end)
          end

          move_next(1)
        end)
      end)
    else
      vim.uv.fs_rename(src, dst, function(err) pcall(_next, err) end)
    end
  end)
end

function M.cp(opts, _next)
  local src = Path.new(opts.src):os_path()
  local dst = Path.new(opts.dst):os_path()
  local flags = opts.flags or {}

  if Path.new(src):is_directory() then
    assert(flags.r, "cannot copy directory without -r flag: " .. src)

    M.mkdir({
      path = dst,
      flags = { p = true },
    }, function()
      _read_dir_iter({
        path = src,
      }, function(err_iter, iter)
        if err_iter then
          pcall(_next, err_iter)
          return
        end

        local entries = {}
        for _, e in iter do
          table.insert(entries, e)
        end

        local function copy_next(index)
          if index > #entries then
            pcall(_next)
            return
          end

          M.cp({
            src = Path.new(src):join(entries[index].name):os_path(),
            dst = Path.new(dst):join(entries[index].name):os_path(),
            flags = flags,
          }, function(err)
            if err then
              pcall(_next, err)
              return
            end
            copy_next(index + 1)
          end)
        end

        copy_next(1)
      end)
    end)
  else
    M.mkdir({
      path = Path.new(dst):parent():os_path(),
      flags = { p = true },
    }, function()
      vim.uv.fs_copyfile(src, dst, function(err) pcall(_next, err) end)
    end)
  end
end

function M.create(opts, _next)
  M.mkdir({
    path = Path.new(opts.path):parent():os_path(),
    flags = { p = true },
  }, function(err)
    if err then
      pcall(_next, err)
      return
    end

    if Path.new(opts.path):is_directory() then
      M.mkdir({ path = opts.path }, _next)
    else
      M.touch({ path = opts.path }, _next)
    end
  end)
end

function M.delete(opts, _next)
  M.rm({
    path = opts.path,
    flags = { r = true },
  }, function(err)
    if err then
      pcall(_next, err)
      return
    end

    vim.schedule(function() hooks.on_delete(opts.path) end)

    pcall(_next)
  end)
end

function M.move(opts, _next)
  M.mv({
    src = opts.src,
    dst = opts.dst,
  }, function(err)
    if err then
      pcall(_next, err)
      return
    end

    vim.schedule(function() hooks.on_rename(opts.src, opts.dst) end)

    pcall(_next)
  end)
end

function M.copy(opts, _next)
  M.cp({
    src = Path.new(opts.src):os_path(),
    dst = Path.new(opts.dst):os_path(),
    flags = { r = true },
  }, _next)
end

function M.trash(...)
  local trash = require("fyler.lib.trash")
  if trash then
    trash.dump(...)
  else
    vim.notify_once("TRASH is supported for this platform, fallback to DELETE", vim.log.levels.WARN)
    M.delete(...)
  end
end

return M


================================================
FILE: lua/fyler/lib/git.lua
================================================
local Path = require("fyler.lib.path")
local Process = require("fyler.lib.process")
local config = require("fyler.config")
local util = require("fyler.lib.util")

local M = {}

local icon_map = {
  ["??"] = "Untracked",
  ["A "] = "Added",
  ["AM"] = "Added",
  [" M"] = "Modified",
  ["MM"] = "Modified",
  ["M "] = "Modified",
  [" D"] = "Deleted",
  ["D "] = "Deleted",
  ["MD"] = "Deleted",
  ["AD"] = "Deleted",
  ["R "] = "Renamed",
  ["RM"] = "Renamed",
  ["RD"] = "Renamed",
  ["C "] = "Copied",
  ["CM"] = "Copied",
  ["CD"] = "Copied",
  ["DD"] = "Conflict",
  ["AU"] = "Conflict",
  ["UD"] = "Conflict",
  ["UA"] = "Conflict",
  ["DU"] = "Conflict",
  ["AA"] = "Conflict",
  ["UU"] = "Conflict",
  ["!!"] = "Ignored",
}

local hl_map = {
  Untracked = "FylerGitUntracked",
  Added = "FylerGitAdded",
  Modified = "FylerGitModified",
  Deleted = "FylerGitDeleted",
  Renamed = "FylerGitRenamed",
  Copied = "FylerGitCopied",
  Conflict = "FylerGitConflict",
  Ignored = "FylerGitIgnored",
}

function M.map_entries_async(root_dir, entries, _next)
  M.build_modified_lookup_for_async(root_dir, function(modified_lookup)
    M.build_ignored_lookup_for_async(root_dir, entries, function(ignored_lookup)
      local status_map = util.tbl_merge_force(modified_lookup, ignored_lookup)
      local result = util.tbl_map(
        entries,
        function(e)
          return {
            config.values.views.finder.columns.git.symbols[icon_map[status_map[e]]] or "",
            hl_map[icon_map[status_map[e]]],
          }
        end
      )
      _next(result)
    end)
  end)
end

---@param dir string
---@param _next function
function M.build_modified_lookup_for_async(dir, _next)
  local process = Process.new({
    path = "git",
    args = { "-C", dir, "status", "--porcelain" },
  })

  process:spawn_async(function(code)
    local lookup = {}

    if code == 0 then
      for _, line in process:stdout_iter() do
        if line ~= "" then
          local symbol = line:sub(1, 2)
          local path = Path.new(dir):join(line:sub(4)):os_path()
          lookup[path] = symbol
        end
      end
    end

    _next(lookup)
  end)
end

---@param dir string
---@param stdin string|string[]
---@param _next function
function M.build_ignored_lookup_for_async(dir, stdin, _next)
  local process = Process.new({
    path = "git",
    args = { "-C", dir, "check-ignore", "--stdin" },
    stdin = table.concat(util.tbl_wrap(stdin), "\n"),
  })

  process:spawn_async(function(code)
    local lookup = {}

    if code == 0 then
      for _, line in process:stdout_iter() do
        if line ~= "" then lookup[line] = "!!" end
      end
    end

    _next(lookup)
  end)
end

return M


================================================
FILE: lua/fyler/lib/hl.lua
================================================
local M = {}

---@param dec integer
local function to_hex(dec) return string.format("%06X", math.max(0, math.min(0xFFFFFF, math.floor(dec)))) end

---@param name string
---@return string|nil
local function get_fg(name)
  local color = vim.api.nvim_get_hl(0, { name = name })
  if color["link"] then
    return get_fg(color["link"])
  elseif color["reverse"] and color["bg"] then
    return "#" .. to_hex(color["bg"])
  elseif color["fg"] then
    return "#" .. to_hex(color["fg"])
  end
end

---@param name string
---@return string|nil
local function get_bg(name)
  local color = vim.api.nvim_get_hl(0, { name = name })
  if color["link"] then
    return get_bg(color["link"])
  elseif color["reverse"] and color["fg"] then
    return "#" .. to_hex(color["fg"])
  elseif color["bg"] then
    return "#" .. to_hex(color["bg"])
  end
end

---@class Palette
---@field bg string
---@field black string
---@field blue string
---@field cyan string
---@field dark_grey string
---@field fg string
---@field green string
---@field grey string
---@field orange string
---@field red string
---@field white string
---@field yellow string

---@return Palette
local function build_palette()
  -- stylua: ignore start
  return {
    black     = "#000000",
    white     = "#ffffff",

    bg        = get_bg("Normal"),
    blue      = get_fg("Directory"),
    cyan      = get_fg("Operator"),
    dark_grey = get_fg("WhiteSpace"),
    fg        = get_fg("Normal"),
    green     = get_fg("String"),
    grey      = get_fg("Comment"),
    orange    = get_fg("SpecialChar"),
    red       = get_fg("Error"),
    yellow    = get_fg("WarningMsg"),
  }
  -- stylua: ignore end
end

function M.setup()
  local palette = build_palette()

  -- stylua: ignore start
  local hl_groups = {
    FylerBlue            = { fg = palette.blue },
    FylerGreen           = { fg = palette.green },
    FylerGrey            = { fg = palette.grey },
    FylerRed             = { fg = palette.red },
    FylerYellow          = { fg = palette.yellow },

    FylerFSDirectoryIcon = { fg = palette.blue },
    FylerFSDirectoryName = { fg = palette.fg },
    FylerFSFile          = { fg = palette.white },
    FylerFSLink          = { fg = palette.grey },

    FylerGitAdded        = { fg = palette.green },
    FylerGitConflict     = { fg = palette.red },
    FylerGitDeleted      = { fg = palette.red },
    FylerGitIgnored      = { fg = palette.grey },
    FylerGitModified     = { fg = palette.yellow },
    FylerGitRenamed      = { fg = palette.yellow },
    FylerGitStaged       = { fg = palette.green },
    FylerGitUnstaged     = { fg = palette.orange },
    FylerGitUntracked    = { fg = palette.cyan },

    FylerWinPick         = { fg = palette.white, bg = palette.blue },

    -- Groups with link must be after non-linked
    FylerNormal           = { link = "Normal" },
    FylerNormalNC         = { link = "NormalNC" },
    FylerBorder           = { link = "FylerNormal" },

    FylerIndentMarker     = { link = "FylerGrey" },

    FylerDiagnosticError  = { link = "DiagnosticError" },
    FylerDiagnosticWarn   = { link = "DiagnosticWarn" },
    FylerDiagnosticInfo   = { link = "DiagnosticInfo" },
    FylerDiagnosticHint   = { link = "DiagnosticHint" },
  }
  -- stylua: ignore end

  require("fyler.hooks").on_highlight(hl_groups, palette)

  for k, v in pairs(hl_groups) do
    vim.api.nvim_set_hl(0, k, vim.tbl_extend("keep", v, { default = true }))
  end
end

return M


================================================
FILE: lua/fyler/lib/path.lua
================================================
local util = require("fyler.lib.util")

---@class Path
---@field _original string
---@field _segments string[]|nil
local Path = {}
Path.__index = Path

---@return boolean
function Path.is_macos() return vim.uv.os_uname().sysname == "Darwin" end

---@return boolean
function Path.is_windows() return vim.uv.os_uname().sysname == "Windows_NT" end

---@return boolean
function Path.is_linux() return not (Path.is_macos() or Path.is_windows()) end

---@param path string
---@return Path
function Path.new(path)
  return setmetatable({
    _original = string.gsub(string.gsub(path, "^%s+", ""), "%s+$", ""),
    _segments = nil,
  }, Path)
end

---@return string[]
function Path:segments()
  if not self._segments then
    local abs = self:posix_path()
    local parts = vim.split(abs, "/", { plain = true })
    self._segments = util.filter_bl(parts)
  end
  return self._segments
end

---@return Path
function Path:parent() return Path.new(vim.fn.fnamemodify(vim.fs.normalize(self:posix_path()), ":h")) end

---@return string
function Path:basename()
  local segments = self:segments()
  return segments[#segments] or ""
end

---@return string
function Path:os_path()
  local path = self._original
  if Path.is_windows() then
    if vim.startswith(path, "/") then
      local drive = path:match("^/(%a+)")
      if drive then return string.format("%s:%s", drive, path:sub(drive:len() + 2):gsub("/", "\\")) end
    end
    return util.select_n(1, path:gsub("/", "\\"))
  else
    return util.select_n(1, path:gsub("\\", "/"))
  end
end

---@return string
function Path:posix_path()
  local path = self._original
  if Path.is_windows() then
    local drive, remaining = path:match("^([^:]+):\\(.*)$")
    if drive then return string.format("/%s/%s", drive:upper(), remaining:gsub("\\", "/")) end
    return util.select_n(1, path:gsub("\\", "/"))
  else
    return path
  end
end

---@return boolean
function Path:exists() return not not util.select_n(1, vim.uv.fs_stat(self:os_path())) end

---@return uv.fs_stat.result|nil
function Path:stats() return util.select_n(1, vim.uv.fs_stat(self:os_path())) end

---@return uv.fs_stat.result|nil
function Path:lstats() return util.select_n(1, vim.uv.fs_lstat(self:os_path())) end

---@return string|nil
function Path:type()
  local stat = self:lstats()
  if not stat then return end
  return stat.type
end

---@return boolean
function Path:is_link() return self:type() == "link" end

---@return boolean
function Path:is_file() return self:type() == "file" end

---@return boolean
function Path:is_directory()
  local t = self:type()
  if t then return t == "directory" end
  if Path.is_windows() then
    return vim.endswith(self._original, "\\")
  else
    return vim.endswith(self._original, "/")
  end
end

---@return boolean
function Path:is_absolute()
  if Path.is_windows() then
    -- Windows: check for drive letter or UNC path
    return self._original:match("^[A-Za-z]:") or self._original:match("^\\\\")
  else
    -- Unix: check for leading /
    return vim.startswith(self._original, "/")
  end
end

---@param ref string
---@return string|nil
function Path:relative(ref) return vim.fs.relpath(self:posix_path(), Path.new(ref):posix_path()) end

---@return Path
function Path:join(...) return Path.new(vim.fs.joinpath(self:posix_path(), ...)) end

---@param other string
---@return boolean
function Path:is_descendant_of(other)
  local other_path = Path.new(other)
  local self_segments = self:segments()
  local other_segments = other_path:segments()
  if #other_segments >= #self_segments then return false end
  for i = 1, #other_segments do
    if self_segments[i] ~= other_segments[i] then return false end
  end
  return true
end

---@param other string
---@return boolean
function Path:is_ancestor_of(other) return Path.new(other):is_descendant_of(self:posix_path()) end

---@return string|nil, string|nil
function Path:res_link()
  if not self:is_link() then return end

  local os_path = self:os_path()
  local current = Path.new(os_path)
  local visited = {}
  while current:is_link() do
    if visited[os_path] then return nil, "circular symlink" end
    visited[os_path] = true

    local read_link = vim.uv.fs_readlink(os_path)
    if not read_link then break end

    if not Path.new(read_link):is_absolute() then
      os_path = current:parent():join(read_link):os_path()
    else
      os_path = read_link
    end

    current = Path.new(os_path)
  end

  return os_path, (Path.new(os_path):lstats() or {}).type
end

---@return fun(): boolean|nil, string|nil
function Path:iter()
  local segments = self:segments()
  local i = 0
  return function()
    i = i + 1
    if i <= #segments then
      local path_parts = {}
      for j = 1, i do
        table.insert(path_parts, segments[j])
      end
      return i == #segments, table.concat({ "", util.unpack(path_parts) }, "/")
    end
  end
end

return Path


================================================
FILE: lua/fyler/lib/process.lua
================================================
---@class ProcessOpts
---@field path string
---@field args string[]|nil
---@field stdin string|nil

---@class Process
---@field pid integer
---@field handle uv.uv_process_t
---@field path string
---@field args string[]|nil
---@field stdin string|nil
---@field stdout string|nil
---@field stderr string|nil
local Process = {}
Process.__index = Process

---@param options ProcessOpts
---@return Process
function Process.new(options)
  local instance = {
    path = options.path,
    args = options.args,
    stdin = options.stdin,
    stdio = {},
  }

  setmetatable(instance, Process)

  return instance
end

---@return Process
function Process:spawn()
  local out = vim.system(vim.list_extend({ self.path }, self.args), { text = true, stdin = self.stdin }):wait()
  self.code = out.code
  self.signal = out.signal
  self.stdout = out.stdout
  self.stderr = out.stderr

  return self
end

function Process:spawn_async(on_exit)
  assert(vim.fn.executable(self.path) == 1, string.format("executable not found: %s", self.path))

  local options = {
    args = self.args,
    stdio = {
      vim.uv.new_pipe(false),
      vim.uv.new_pipe(false),
      vim.uv.new_pipe(false),
    },
  }

  self.handle, self.pid = vim.uv.spawn(self.path, options, on_exit)

  vim.uv.write(options.stdio[1], self.stdin or "", function() vim.uv.close(options.stdio[1]) end)

  vim.uv.read_start(options.stdio[2], function(_, data)
    self.stdout = self.stdout or ""
    if data then
      self.stdout = self.stdout .. data
    else
      vim.uv.read_stop(options.stdio[2])
      vim.uv.close(options.stdio[2])
    end
  end)

  vim.uv.read_start(options.stdio[3], function(_, data)
    self.stderr = self.stderr or ""
    if data then
      self.stderr = self.stderr .. data
    else
      vim.uv.read_stop(options.stdio[3])
      vim.uv.close(options.stdio[3])
    end
  end)
end

---@return boolean
function Process:is_running() return vim.uv.is_active(self.handle) == true end

---@return string
function Process:out() return self.stdout end

---@return string
function Process:err() return self.stderr end

function Process:stdout_iter()
  if not self.stdout then
    return function() end
  end

  local lines = vim.split(self.stdout, "\n")
  local i = 0
  return function()
    i = i + 1
    if i <= #lines then return i, lines[i] end
  end
end

function Process:stderr_iter()
  if not self.stderr then return end

  local lines = vim.split(self.stderr, "\n")
  local i = 0
  return function()
    i = i + 1
    if i <= #lines then return i, lines[i] end
  end
end

return Process


================================================
FILE: lua/fyler/lib/spinner.lua
================================================
local util = require("fyler.lib.util")

---@class Spinner
---@field text string
---@field count number
---@field interval number
---@field pattern string[]
---@field timer any
local Spinner = {}
Spinner.__index = Spinner

---@return Spinner
function Spinner.new(text)
  local instance = {
    text = util.str_truncate(text, vim.v.echospace - 2, "..."),
    interval = 100,
    count = 0,
    timer = nil,
    pattern = {
      "⠋",
      "⠙",
      "⠹",
      "⠸",
      "⠼",
      "⠴",
      "⠦",
      "⠧",
      "⠇",
      "⠏",
    },
  }

  return setmetatable(instance, Spinner)
end

---@param text string
function Spinner:set_text(text) self.text = text end

function Spinner:start()
  if not self.timer then
    self.cmdheight = vim.o.cmdheight
    if self.cmdheight == 0 then vim.o.cmdheight = 1 end

    self.timer = assert(vim.uv.new_timer())
    self.timer:start(
      250,
      self.interval,
      vim.schedule_wrap(function()
        self.count = self.count + 1
        local step = self.pattern[(self.count % #self.pattern) + 1]
        vim.cmd(string.format("echo '%s %s' | redraw", step, self.text))
      end)
    )
  end
end

function Spinner:stop()
  if self.timer then
    local timer = self.timer
    self.timer = nil
    timer:stop()

    if not timer:is_closing() then timer:close() end
  end

  vim.schedule(function()
    vim.cmd("redraw | echomsg ''")

    if self.cmdheight then
      vim.o.cmdheight = self.cmdheight
      self.cmdheight = nil
    end
  end)
end

return Spinner


================================================
FILE: lua/fyler/lib/structs/list.lua
================================================
---@class LinkedList
---@field node LinkedListNode
local LinkedList = {}
LinkedList.__index = LinkedList

---@class LinkedListNode
---@field next LinkedListNode|nil
---@field data any
local LinkedListNode = {}
LinkedListNode.__index = LinkedListNode

---@return LinkedList
function LinkedList.new() return setmetatable({}, LinkedList) end

---@return integer
function LinkedList:len()
  local count = 0
  local current = self.node
  while current do
    count = count + 1
    current = current.next
  end

  return count
end

---@param fn fun(node: LinkedListNode)
function LinkedList:each(fn)
  local start = self.node
  while start do
    fn(start.data)
    start = start.next
  end
end

---@param pos integer
---@param data any
function LinkedList:insert(pos, data)
  local newNode = setmetatable({ data = data }, LinkedListNode)
  if pos == 1 then
    newNode.next = self.node
    self.node = newNode
    return
  end

  local start = self.node
  for _ = 1, pos - 2 do
    if not start then error("position is out of bound") end
    start = start.next
  end

  if not start then error("position is out of bound") end

  newNode.next = start.next
  start.next = newNode
end

---@param pos integer
function LinkedList:erase(pos)
  assert(pos >= 1, "position must be 1 or greater")

  if not self.node then error("list is empty") end

  if pos == 1 then
    self.node = self.node.next
    return
  end

  local start = self.node
  for _ = 1, pos - 2 do
    if not start or not start.next then error("position is out of bound") end

    start = start.next
  end

  if not start or not start.next then error("position is out of bound") end

  start.next = start.next.next
end

---@return table
function LinkedList:totable()
  local tbl = {}
  self:each(function(item) table.insert(tbl, item) end)

  return tbl
end

return LinkedList


================================================
FILE: lua/fyler/lib/structs/stack.lua
================================================
---@class Stack
---@field items table
local Stack = {}
Stack.__index = Stack

---@return Stack
function Stack.new() return setmetatable({ items = {} }, Stack) end

---@param data any
function Stack:push(data) table.insert(self.items, data) end

function Stack:pop()
  assert(not self:is_empty(), "stack is empty")

  return table.remove(self.items)
end

---@return any
function Stack:top()
  assert(not self:is_empty(), "stack is empty")
  return self.items[#self.items]
end

---@return integer
function Stack:size() return #self.items end

---@return boolean
function Stack:is_empty() return #self.items == 0 end

return Stack


================================================
FILE: lua/fyler/lib/structs/trie.lua
================================================
---@class Trie
---@field value any
---@field children table<string, Trie>
local Trie = {}
Trie.__index = Trie

---@param value any|nil
---@return Trie
function Trie.new(value)
  local instance = {
    value = value,
    children = {},
  }
  setmetatable(instance, Trie)
  return instance
end

---@param segments string[]
---@param value any
---@return Trie -- returns the final node
function Trie:insert(segments, value)
  if #segments == 0 then
    if type(value) == "function" then
      self.value = value(self.value)
    else
      self.value = value
    end

    return self
  end

  local head = segments[1]
  if not self.children[head] then self.children[head] = Trie.new() end

  local rest = {}
  for i = 2, #segments do
    rest[#rest + 1] = segments[i]
  end

  return self.children[head]:insert(rest, value)
end

---@param segments string[]
---@return Trie|nil
function Trie:find(segments)
  if #segments == 0 then return self end

  local head = segments[1]
  if not self.children[head] then return nil end

  local rest = {}
  for i = 2, #segments do
    rest[#rest + 1] = segments[i]
  end

  return self.children[head]:find(rest)
end

---@param segments string[]
---@return boolean -- true if deleted, false if not found
function Trie:delete(segments)
  if #segments == 0 then return false end

  if #segments == 1 then
    local head = segments[1]
    if self.children[head] then
      self.children[head] = nil
      return true
    end
    return false
  end

  local head = segments[1]
  if not self.children[head] then return false end

  local rest = {}
  for i = 2, #segments do
    rest[#rest + 1] = segments[i]
  end

  return self.children[head]:delete(rest)
end

---@param fn fun(node: Trie): boolean|nil
function Trie:dfs(fn)
  if fn(self) == false then return end

  for _, child in pairs(self.children) do
    child:dfs(fn)
  end
end

return Trie


================================================
FILE: lua/fyler/lib/trash/init.lua
================================================
local M = setmetatable({}, {
  __index = function(_, key)
    local Path = require("fyler.lib.path")
    if Path.is_windows() then
      return require("fyler.lib.trash.windows")[key]
    elseif Path.is_macos() then
      return require("fyler.lib.trash.macos")[key]
    else
      return require("fyler.lib.trash.linux")[key]
    end
  end,
})

return M


================================================
FILE: lua/fyler/lib/trash/linux.lua
================================================
local Path = require("fyler.lib.path")
local fs = require("fyler.lib.fs")

local M = {}

---@param opts {dir: string, basename: string}
---@return string
function M.next_name(opts)
  if not Path.new(opts.dir):join(opts.basename):exists() then return opts.basename end

  local name, extension = vim.fn.fnamemodify(opts.basename, ":r"), vim.fn.fnamemodify(opts.basename, ":e")
  local counter = 1
  while true do
    local candidate = string.format("%s (%d).%s", name, counter, extension)
    if not Path.new(opts.dir):join(candidate):exists() then return candidate end
    counter = counter + 1
  end
end

function M.dump(opts, _next)
  local path_to_trash = Path.new(opts.path)
  local dir = Path.new(vim.F.if_nil(vim.env.XDG_DATA_HOME, vim.fs.joinpath(vim.fn.expand("$HOME"), ".local", "share")))
    :join("Trash")
  local files, info = dir:join("files"):os_path(), dir:join("info"):os_path()

  fs.mkdir({
    path = files,
    flags = { p = true },
  }, function(err)
    if err then return pcall(_next, err) end

    fs.mkdir({
      path = info,
      flags = { p = true },
    }, function(err_info)
      if err_info then return pcall(_next, err_info) end

      local target_name = M.next_name({
        dir = files,
        basename = path_to_trash:basename(),
      })
      local target_path = Path.new(files):join(target_name)
      local trash_info = table.concat({
        "[Trash Info]",
        string.format("Path=%s", path_to_trash:os_path()),
        string.format("DeletionDate=%s", os.date("%Y-%m-%dT%H:%M:%S")),
      }, "\n")

      -- Writing meta data to "%.trashinfo"
      fs.write({
        path = Path.new(info):join(target_name .. ".trashinfo"):os_path(),
        data = trash_info,
      }, function(err_write)
        if err_write then return pcall(_next, err_write) end

        -- Move to trash directory
        fs.mv({
          src = path_to_trash:os_path(),
          dst = target_path:os_path(),
        }, function(err_mv) return pcall(_next, err_mv) end)
      end)
    end)
  end)
end

return M


================================================
FILE: lua/fyler/lib/trash/macos.lua
================================================
local Path = require("fyler.lib.path")

local M = {}

function M.dump(opts, _next)
  local abspath = Path.new(opts.path):os_path()
  local Process = require("fyler.lib.process")
  local proc

  -- Built-in trash command available on macOS 15 and later
  proc = Process.new({
    path = "/usr/bin/trash",
    args = { abspath },
  })

  proc:spawn_async(function(code)
    vim.schedule(function()
      if code == 0 then
        pcall(_next)
      else
        pcall(_next, "failed to move to trash: " .. (proc:err() or ""))
      end
    end)
  end)
end

return M


================================================
FILE: lua/fyler/lib/trash/windows.lua
================================================
local Path = require("fyler.lib.path")
local M = {}

function M.dump(opts, _next)
  local abspath = Path.new(opts.path):os_path()
  local ps_script = string.format(
    [[
      $timeoutSeconds = 30;
      $job = Start-Job -ScriptBlock {
        Add-Type -AssemblyName Microsoft.VisualBasic;
        $ErrorActionPreference = 'Stop';
        $item = Get-Item -LiteralPath '%s';
        if ($item.PSIsContainer) {
          [Microsoft.VisualBasic.FileIO.FileSystem]::DeleteDirectory('%s', 'OnlyErrorDialogs', 'SendToRecycleBin');
        } else {
          [Microsoft.VisualBasic.FileIO.FileSystem]::DeleteFile('%s', 'OnlyErrorDialogs', 'SendToRecycleBin');
        }
      };
      $completed = Wait-Job -Job $job -Timeout $timeoutSeconds;
      if ($completed) {
        $result = Receive-Job -Job $job -ErrorAction SilentlyContinue -ErrorVariable jobError;
        Remove-Job -Job $job -Force;
        if ($jobError) {
          Write-Error $jobError;
          exit 1;
        }
      } else {
        Remove-Job -Job $job -Force;
        Write-Error 'Operation timed out after 30 seconds';
        exit 1;
      }
    ]],
    abspath,
    abspath,
    abspath
  )

  local Process = require("fyler.lib.process")
  local proc = Process.new({
    path = "powershell",
    args = { "-NoProfile", "-NonInteractive", "-Command", ps_script },
  })

  proc:spawn_async(function(code)
    vim.schedule(function()
      if code == 0 then
        pcall(_next)
      else
        pcall(_next, "failed to move to recycle bin: " .. (proc:err() or ""))
      end
    end)
  end)
end

return M


================================================
FILE: lua/fyler/lib/ui/component.lua
================================================
local util = require("fyler.lib.util")

---@class UiComponentOption
---@field highlight string|nil
---@field virt_text string[][]|nil
---@field col integer|nil

---@class UiComponent
---@field tag string
---@field value any
---@field parent UiComponent
---@field option UiComponentOption
---@field children UiComponent[]
local UiComponent = {}

---@param fn fun(...): table
---@return UiComponent
function UiComponent.new(fn)
  local instance = {}

  local mt = {
    __call = function(_, ...)
      local this = fn(...)

      setmetatable(this, { __index = UiComponent })

      return this
    end,

    __index = function(_, name) return rawget(UiComponent, name) end,
  }

  setmetatable(instance, mt)

  return instance
end

---@param fn fun(...)
---@return function
function UiComponent.new_async(fn)
  return function(...)
    local args = { ... }
    local cb = table.remove(args)

    table.insert(args, function(this, ...) cb(setmetatable(this, { __index = UiComponent }), ...) end)

    fn(util.unpack(args))
  end
end

function UiComponent:width()
  if self.tag == "text" then return string.len(self.value or self.option.virt_text[1][1]) end

  if self.tag == "row" then
    local width = 0
    for i = 1, #self.children do
      width = width + self.children[i]:width()
    end

    return width
  end

  if self.children then
    local width = 0
    for i = 1, #self.children do
      local c_width = self.children[i]:width()
      if c_width > width then width = c_width end
    end

    return width
  end

  error("UNIMPLEMENTED")
end

function UiComponent:height()
  if self.tag == "text" then return 1 end

  if self.tag == "row" then
    local max_height = 0
    for i = 1, #self.children do
      local child_height = self.children[i]:height()
      if child_height > max_height then max_height = child_height end
    end
    return max_height
  end

  if self.children then
    local total_height = 0
    for i = 1, #self.children do
      total_height = total_height + self.children[i]:height()
    end
    return total_height
  end

  error("UNIMPLEMENTED")
end

return UiComponent


================================================
FILE: lua/fyler/lib/ui/init.lua
================================================
local Component = require("fyler.lib.ui.component")
local Renderer = require("fyler.lib.ui.renderer")

---@class Ui
---@field win Win
---@field renderer UiRenderer
local Ui = {}
Ui.__index = Ui

Ui.Component = Component

---@param children UiComponent[]
Ui.Column = Ui.Component.new(function(children)
  return {
    tag = "column",
    children = children,
  }
end)

---@param children UiComponent[]
Ui.Row = Ui.Component.new(function(children)
  return {
    tag = "row",
    children = children,
  }
end)

Ui.Text = Ui.Component.new(
  function(value, option)
    return {
      tag = "text",
      value = value,
      option = option,
      children = {},
    }
  end
)

---@param win Win
---@return Ui
function Ui.new(win) return setmetatable({ win = win, renderer = Renderer.new() }, Ui) end

---@param component UiComponent
Ui.render = vim.schedule_wrap(function(self, component, ...)
  local opts = {}
  local onrender = nil

  for i = 1, select("#", ...) do
    local arg = select(i, ...)

    if type(arg) == "table" then
      opts = arg
    elseif type(arg) == "function" then
      onrender = arg
    end
  end

  -- Render Ui components to neovim api compatible
  self.renderer:render(component)

  if not opts.partial then
    -- Clear namespace and sets renderer lines from given Ui component
    self.win:set_lines(0, -1, self.renderer.line)
  end

  for _, highlight in ipairs(self.renderer.highlight) do
    -- stylua: ignore start
    self.win:set_extmark(highlight.line, highlight.col_start, {
      end_col  = highlight.col_end,
      hl_group = highlight.highlight_group,
    })
    -- stylua: ignore end
  end

  for _, extmark in ipairs(self.renderer.extmark) do
    -- stylua: ignore start
    self.win:set_extmark(extmark.line, 0, {
      hl_mode           = extmark.hl_mode,
      virt_text         = extmark.virt_text,
      virt_text_pos     = extmark.virt_text_pos,
      virt_text_win_col = extmark.col,
    })
    -- stylua: ignore end
  end

  pcall(onrender)
end)

return Ui


================================================
FILE: lua/fyler/lib/ui/renderer.lua
================================================
---@class UiRenderer
---@field line string[]
---@field extmark table[]
---@field highlight table[]
---@field flag table
local Renderer = {}
Renderer.__index = Renderer

function Renderer.new()
  local instance = {
    line = {},
    extmark = {},
    highlight = {},
    flag = {
      in_row = false,
      row_base_line = 0, -- Track the starting line of current row
      column_offset = 0, -- Track horizontal offset for columns in row
    },
  }

  setmetatable(instance, Renderer)

  return instance
end

---@param component UiComponent
function Renderer:render(component)
  self.line = {}
  self.extmark = {}
  self.highlight = {}
  self.flag = {
    in_row = false,
    row_base_line = 0,
    column_offset = 0,
  }
  self:_render(component)
end

---@param component UiComponent
---@param current_col number|nil
---@return string|nil, number|nil
function Renderer:_render_text(component, current_col)
  local text_value = tostring(component.value or "")
  local highlight = component.option and component.option.highlight
  local width = #text_value

  if self.flag.in_row then
    current_col = current_col or 0

    if component.option and component.option.virt_text then
      width = #component.option.virt_text[1][1]
      table.insert(self.extmark, {
        line = self.flag.row_base_line,
        col = current_col,
        virt_text = component.option.virt_text,
        virt_text_pos = "overlay",
        hl_mode = "combine",
      })
    end

    if highlight then
      table.insert(self.highlight, {
        line = self.flag.row_base_line,
        col_start = current_col,
        col_end = current_col + #text_value,
        highlight_group = highlight,
      })
    end

    return text_value, current_col + width
  else
    if text_value then table.insert(self.line, text_value) end

    -- Now calculate line number after adding the text
    local current_line_idx = #self.line - 1

    if component.option and component.option.virt_text then
      table.insert(self.extmark, {
        line = current_line_idx,
        col = component.option.col or 0,
        virt_text = component.option.virt_text,
        virt_text_pos = "overlay",
        hl_mode = "combine",
      })
    end

    if highlight then
      table.insert(self.highlight, {
        line = current_line_idx,
        col_start = 0,
        col_end = #text_value,
        highlight_group = highlight,
      })
    end
  end
end

---@param component UiComponent
---@param current_col number|nil
---@return string|nil, number|nil
function Renderer:_render_nested_row_in_row(component, current_col)
  current_col = current_col or 0
  local nested_row_content = {}

  -- Render the nested row's children inline
  for _, child in ipairs(component.children) do
    if child.tag == "row" then error("Rows cannot be nested more than one level deep") end

    local text_part, new_col = self:_render_child_in_row(child, current_col)
    if text_part then
      table.insert(nested_row_content, text_part)
      current_col = new_col or current_col
    end
  end

  -- Concatenate all parts of the nested row
  local nested_row_text = table.concat(nested_row_content)

  return nested_row_text, current_col
end

---@param component UiComponent
---@param current_col number|nil
---@return string|nil, number|nil
function Renderer:_render_child_in_row(component, current_col)
  if component.tag == "text" then
    return self:_render_text(component, current_col)
  elseif component.tag == "column" then
    return self:_render_column_in_row(component, current_col)
  elseif component.tag == "row" then
    return self:_render_nested_row_in_row(component, current_col)
  else
    error("The row component does not support having a `" .. component.tag .. "` as a child")
  end
end

---@param component UiComponent
---@param current_col number|nil
---@return string|nil, number|nil
function Renderer:_render_column_in_row(component, current_col)
  current_col = current_col or 0
  local column_start_col = current_col
  local column_width = component:width()
  local column_lines = {}
  local column_highlights = {}
  local column_extmarks = {}

  -- Store current state
  local saved_line = vim.deepcopy(self.line)
  local saved_highlights = vim.deepcopy(self.highlight)
  local saved_extmarks = vim.deepcopy(self.extmark)
  local saved_flag = vim.deepcopy(self.flag)

  -- Reset for column rendering
  self.line = {}
  self.highlight = {}
  self.extmark = {}
  self.flag.in_row = false
  self.flag.column_offset = column_start_col

  -- Render column children
  for _, child in ipairs(component.children) do
    self:_render_child(child)
  end

  -- Capture column results
  column_lines = vim.deepcopy(self.line)
  column_highlights = vim.deepcopy(self.highlight)
  column_extmarks = vim.deepcopy(self.extmark)

  -- Restore state
  self.line = saved_line
  self.highlight = saved_highlights
  self.extmark = saved_extmarks
  self.flag = saved_flag

  -- Ensure we have enough lines in the main buffer
  local lines_needed = self.flag.row_base_line + #column_lines
  while #self.line < lines_needed do
    table.insert(self.line, "")
  end

  -- Apply column content to the main buffer with offset
  for i, line_content in ipairs(column_lines) do
    local target_line_idx = self.flag.row_base_line + i
    local current_line = self.line[target_line_idx] or ""

    -- Only add padding and content if the column actually has content
    if line_content and line_content ~= "" then
      -- Pad current line to reach column start position
      local padding_needed = column_start_col - #current_line
      if padding_needed > 0 then current_line = current_line .. string.rep(" ", padding_needed) end

      -- Append column content
      self.line[target_line_idx] = current_line .. line_content
    end
  end

  -- Apply column highlights with offset
  for _, hl in ipairs(column_highlights) do
    table.insert(self.highlight, {
      line = self.flag.row_base_line + hl.line,
      col_start = column_start_col + hl.col_start,
      col_end = column_start_col + hl.col_end,
      highlight_group = hl.highlight_group,
    })
  end

  -- Apply column extmarks with offset
  for _, extmark in ipairs(column_extmarks) do
    table.insert(self.extmark, {
      line = self.flag.row_base_line + extmark.line,
      col = column_start_col + (extmark.col or 0),
      virt_text = extmark.virt_text,
      virt_text_pos = extmark.virt_text_pos,
      hl_mode = extmark.hl_mode,
    })
  end

  -- Return empty string and new column position
  return "", column_start_col + column_width
end

---@param component UiComponent
function Renderer:_render_row(component)
  self.flag.in_row = true
  self.flag.row_base_line = #self.line

  local current_col = 0
  local max_lines_in_row = 0

  -- First pass: calculate how many lines this row will need
  for _, child in ipairs(component.children) do
    if child.tag == "column" then
      local column_height = 0
      -- Count lines in column by simulating render
      local temp_renderer = Renderer.new()
      temp_renderer:render(child)
      column_height = #temp_renderer.line
      if column_height > max_lines_in_row then max_lines_in_row = column_height end
    else
      max_lines_in_row = math.max(max_lines_in_row, 1)
    end
  end

  -- Ensure we have enough lines
  for _ = 1, max_lines_in_row do
    table.insert(self.line, "")
  end

  -- Second pass: render children
  for _, child in ipairs(component.children) do
    local text_part, new_col = self:_render_child_in_row(child, current_col)
    if text_part and text_part ~= "" then
      -- For text components, update the first line of the row
      local target_line_idx = self.flag.row_base_line + 1
      local current_line = self.line[target_line_idx] or ""
      local padding_needed = current_col - #current_line
      if padding_needed > 0 then current_line = current_line .. string.rep(" ", padding_needed) end
      self.line[target_line_idx] = current_line .. text_part
    end
    current_col = new_col or current_col
  end

  self.flag.in_row = false
end

---@param component UiComponent
function Renderer:_render_column(component)
  for _, child in ipairs(component.children) do
    self:_render_child(child)
  end
end

---@param component UiComponent
function Renderer:_render_child(component)
  if component.tag == "text" then
    self:_render_text(component)
  elseif component.tag == "column" then
    self:_render_column(component)
  elseif component.tag == "row" then
    self:_render_row(component)
  else
    -- Handle unknown components by rendering their children
    if component.children then
      for _, child in ipairs(component.children) do
        self:_render_child(child)
      end
    end
  end
end

---@param component UiComponent
function Renderer:_render(component)
  if component.tag then
    -- If the component has a tag, render it as a specific component
    self:_render_child(component)
  elseif component.children then
    -- If no tag but has children, render children
    for _, child in ipairs(component.children) do
      self:_render_child(child)
    end
  end
end

return Renderer


================================================
FILE: lua/fyler/lib/util.lua
================================================
local M = {}

---@param n integer
---@param ... any
function M.select_n(n, ...)
  local x = select(n, ...)
  return x
end

---@generic T
---@param tbl T[]
---@param start integer|nil
---@param stop integer|nil
---@return T ...
function M.unpack(tbl, start, stop)
  start = start or 1
  stop = stop or #tbl
  if start > stop then return end

  return tbl[start], M.unpack(tbl, start + 1, stop)
end

---@param tbl table
---@param fn function
function M.if_any(tbl, fn) return vim.iter(tbl):any(fn) end

---@param tbl table
---@param fn function
function M.if_all(tbl, fn) return vim.iter(tbl):all(fn) end

---@param value any
---@return table
function M.tbl_wrap(value) return type(value) == "table" and value or { value } end

---@param tbl table
---@param fn function
---@return any
function M.tbl_find(tbl, fn) return vim.iter(tbl):find(fn) end

---@param tbl table
---@param fn function
function M.tbl_map(tbl, fn) return vim.iter(tbl):map(fn):totable() end

---@param tbl table
---@param fn function
function M.tbl_each(tbl, fn) return vim.iter(tbl):each(fn) end

---@param tbl table
---@param fn function
function M.tbl_filter(tbl, fn) return vim.iter(tbl):filter(fn):totable() end

---@param a table
---@param b table
---@return table
function M.tbl_merge_force(a, b) return vim.tbl_deep_extend("force", a, b) end

---@param a table
---@param b table
---@return table
function M.tbl_merge_keep(a, b) return vim.tbl_deep_extend("keep", a, b) end

---@param tbl table
---@return table
function M.unique(tbl)
  local res = {}
  for i = 1, #tbl do
    if tbl[i] and not vim.tbl_contains(res, tbl[i]) then table.insert(res, tbl[i]) end
  end

  return res
end

---@param str string
---@return string
function M.camel_to_snake(str)
  if not str or str == "" then return str end

  local result = str:gsub("(%u)", function(c) return "_" .. c:lower() end)

  if result:sub(1, 1) == "_" then result = result:sub(2) end

  return result
end

---@param lines string[]
function M.filter_bl(lines)
  return vim.iter(lines):filter(function(line) return line ~= "" end):totable()
end

---@param winid number|nil
---@return boolean
function M.is_valid_winid(winid) return type(winid) == "number" and vim.api.nvim_win_is_valid(winid) end

---@param bufnr number|nil
---@return boolean
function M.is_valid_bufnr(bufnr) return type(bufnr) == "number" and vim.api.nvim_buf_is_valid(bufnr) end

---@param winid integer
---@param option string
---@return any
function M.get_win_option(winid, option)
  if M.is_valid_winid(winid) then return vim.api.nvim_get_option_value(option, { win = winid, scope = "local" }) end
end

---@param bufnr integer
---@param option string
---@return any
function M.get_buf_option(bufnr, option)
  if M.is_valid_bufnr(bufnr) then return vim.api.nvim_get_option_value(option, { buf = bufnr, scope = "local" }) end
end

---@param winid integer
---@param option string
---@param value any
function M.set_win_option(winid, option, value)
  if M.is_valid_winid(winid) then vim.api.nvim_set_option_value(option, value, { win = winid, scope = "local" }) end
end

---@param bufnr integer
---@param option string
---@param value any
function M.set_buf_option(bufnr, option, value)
  if M.is_valid_bufnr(bufnr) then vim.api.nvim_set_option_value(option, value, { buf = bufnr, scope = "local" }) end
end

---@param str string
---@param max_length integer
---@param trailing string
function M.str_truncate(str, max_length, trailing)
  trailing = trailing or "..."
  if vim.fn.strdisplaywidth(str) > max_length then str = vim.trim(str:sub(1, max_length - #trailing)) .. trailing end
  return str
end

---@param fn function
---@param ... any
---@return boolean|any
function M.try(fn, ...)
  local ok, result = pcall(fn, ...)
  if not ok then return false end

  return result or true
end

---@type table<string, uv.uv_timer_t>
local running = {}

---@param name string
---@param timeout integer
---@param fn function
function M.debounce(name, timeout, fn)
  if running[name] then running[name]:stop() end

  running[name] = vim.defer_fn(function()
    running[name] = nil

    fn()
  end, timeout)
end

---@param index integer|nil
function M.cmd_history(index) return vim.fn.histget("cmd", index or -1) end

---@return string[]|nil
function M.get_visual_selection()
  local start_mark = vim.api.nvim_buf_get_mark(0, "<")
  local end_mark = vim.api.nvim_buf_get_mark(0, ">")
  local start_row, start_col = start_mark[1], start_mark[2]
  local end_row, end_col = end_mark[1], end_mark[2]
  if start_row == 0 or end_row == 0 then return nil end
  return vim.api.nvim_buf_get_text(0, start_row - 1, start_col, end_row - 1, end_col + 1, {})
end

return M


================================================
FILE: lua/fyler/lib/win.lua
================================================
local util = require("fyler.lib.util")

---@alias WinKind
---| "float"
---| "replace"
---| "split_above"
---| "split_above_all"
---| "split_below"
---| "split_below_all"
---| "split_left"
---| "split_left_most"
---| "split_right"
---| "split_right_most"

---@class Win
---@field augroup integer
---@field autocmds table
---@field border string|string[]
---@field bottom integer|string|nil
---@field buf_opts table
---@field bufname string
---@field bufnr integer|nil
---@field enter boolean
---@field footer string|string[]|nil
---@field footer_pos string|nil
---@field height string
---@field kind WinKind
---@field left integer|string|nil
---@field mappings table
---@field mappings_opts vim.keymap.set.Opts
---@field namespace integer
---@field on_hide function|nil
---@field on_show function|nil
---@field render function|nil
---@field right integer|string|nil
---@field title string|string[]|nil
---@field title_pos string|nil
---@field top integer|string|nil
---@field ui Ui
---@field user_autocmds table
---@field user_mappings table
---@field width integer|string
---@field win integer|nil
---@field win_opts table
---@field winid integer|nil
local Win = {}
Win.__index = Win

---@return Win
function Win.new(opts)
  local instance = util.tbl_merge_keep(opts or {}, { kind = "replace" })
  instance.ui = require("fyler.lib.ui").new(instance)
  setmetatable(instance, Win)
  return instance
end

---@return boolean
function Win:has_valid_winid() return type(self.winid) == "number" and vim.api.nvim_win_is_valid(self.winid) end

---@return boolean
function Win:has_valid_bufnr() return type(self.bufnr) == "number" and vim.api.nvim_buf_is_valid(self.bufnr) end

---@return boolean
function Win:is_visible() return self:has_valid_winid() and self:has_valid_bufnr() end

---@return integer|nil
function Win:winbuf()
  if self:has_valid_winid() then return vim.api.nvim_win_get_buf(self.winid) end
end

---@return integer|nil, integer|nil
function Win:get_cursor()
  if not self:has_valid_winid() then return end

  return util.unpack(vim.api.nvim_win_get_cursor(self.winid))
end

function Win:set_local_buf_option(k, v)
  if self:has_valid_bufnr() then util.set_buf_option(self.bufnr, k, v) end
end

function Win:set_local_win_option(k, v)
  if self:has_valid_winid() then util.set_win_option(self.winid, k, v) end
end

function Win:get_local_buf_option(k)
  if self:has_valid_bufnr() then return util.get_buf_option(self.bufnr, k) end
end

function Win:get_local_win_option(k)
  if self:has_valid_winid() then return util.get_win_option(self.winid, k) end
end

---@param row integer
---@param col integer
function Win:set_cursor(row, col)
  if self:has_valid_winid() then vim.api.nvim_win_set_cursor(self.winid, { row, col }) end
end

---@param start integer
---@param finish integer
---@param lines string[]
function Win:set_lines(start, finish, lines)
  if not self:has_valid_bufnr() then return end

  local was_modifiable = util.get_buf_option(self.bufnr, "modifiable")
  local undolevels = util.get_buf_option(self.bufnr, "undolevels")

  self:set_local_buf_option("modifiable", true)
  self:set_local_buf_option("undolevels", -1)

  vim.api.nvim_buf_clear_namespace(self.bufnr, self.namespace, 0, -1)
  vim.api.nvim_buf_set_lines(self.bufnr, start, finish, false, lines)

  if not was_modifiable then self:set_local_buf_option("modifiable", false) end

  self:set_local_buf_option("modified", false)
  self:set_local_buf_option("undolevels", undolevels)
end

---@param row integer
---@param col integer
---@param options vim.api.keyset.set_extmark
function Win:set_extmark(row, col, options)
  if self:has_valid_bufnr() then vim.api.nvim_buf_set_extmark(self.bufnr, self.namespace, row, col, options) end
end

function Win:focus()
  local windows = vim.fn.win_findbuf(self.bufnr)
  if not windows or not windows[1] then return end

  vim.api.nvim_set_current_win(windows[1])
end

function Win:update_config(config)
  if not self:has_valid_winid() then return end

  local old_config = vim.api.nvim_win_get_config(self.winid)

  vim.api.nvim_win_set_config(self.winid, util.tbl_merge_force(old_config, config))
end

function Win:update_title(title)
  if self.kind:match("^float") then self:update_config({ title = title }) end
end

function Win:config()
  local winconfig = {
    style = "minimal",
  }

  ---@param dim integer|string
  ---@return integer|nil, boolean|nil
  local function resolve_dim(dim)
    if type(dim) == "number" then
      return dim, false
    elseif type(dim) == "string" then
      local is_percentage = dim:match("%%$")
      if is_percentage then
        return tonumber(dim:match("^(.*)%%$")) * 0.01, true
      else
        return tonumber(dim), false
      end
    end
  end

  if self.kind:match("^split_") then
    winconfig.split = self.kind:match("^split_(.*)")
  elseif self.kind:match("^replace") then
    return winconfig
  elseif self.kind:match("^float") then
    winconfig.relative = self.win and "win" or "editor"
    winconfig.border = self.border
    winconfig.title = self.title
    winconfig.title_pos = self.title_pos
    winconfig.footer = self.footer
    winconfig.footer_pos = self.footer_pos
    winconfig.row = 0
    winconfig.col = 0
    winconfig.win = self.win

    if not (not self.top and self.top == "none") then
      local magnitude, is_percentage = resolve_dim(self.top)
      if is_percentage then
        winconfig.row = math.ceil(magnitude * vim.o.lines)
      else
        winconfig.row = magnitude
      end
    end

    if not (not self.right or self.right == "none") then
      local right_magnitude, is_percentage = resolve_dim(self.right)
      local width_magnitude = resolve_dim(self.width)
      if is_percentage then
        winconfig.col = math.ceil((1 - right_magnitude - width_magnitude) * vim.o.columns)
      else
        winconfig.col = (vim.o.columns - right_magnitude - width_magnitude)
      end
    end

    if not (not self.bottom or self.bottom == "none") then
      local bottom_magnitude, is_percentage = resolve_dim(self.bottom)
      local height_magnitude = resolve_dim(self.height)
      if is_percentage then
        winconfig.row = math.ceil((1 - bottom_magnitude - height_magnitude) * vim.o.lines)
      else
        winconfig.row = (vim.o.lines - bottom_magnitude - height_magnitude)
      end
    end

    if not (not self.left and self.left == "none") then
      local magnitude, is_percentage = resolve_dim(self.left)
      if is_percentage then
        winconfig.col = math.ceil(magnitude * vim.o.columns)
      else
        winconfig.col = magnitude
      end
    end
  else
    error(string.format("[fyler.nvim] Invalid window kind `%s`", self.kind))
  end

  if self.width then
    local magnitude, is_percentage = resolve_dim(self.width)
    if is_percentage then
      winconfig.width = math.ceil(magnitude * vim.o.columns)
    else
      winconfig.width = magnitude
    end
  end

  if self.height then
    local magnitude, is_percentage = resolve_dim(self.height)
    if is_percentage then
      winconfig.height = math.ceil(magnitude * vim.o.lines)
    else
      winconfig.height = magnitude
    end
  end

  return winconfig
end

function Win:show()
  local current_bufnr = vim.api.nvim_get_current_buf()
  self.origin_win = vim.api.nvim_get_current_win()

  local win_config = self:config()
  if win_config.split and (win_config.split:match("_all$") or win_config.split:match("_most$")) then
    if win_config.split == "left_most" then
      vim.api.nvim_command(string.format("topleft %dvsplit", win_config.width))
    elseif win_config.split == "above_all" then
      vim.api.nvim_command(string.format("topleft %dsplit", win_config.height))
    elseif win_config.split == "right_most" then
      vim.api.nvim_command(string.format("botright %dvsplit", win_config.width))
    elseif win_config.split == "below_all" then
      vim.api.nvim_command(string.format("botright %dsplit", win_config.height))
    else
      error(string.format("Invalid window kind `%s`", win_config.split))
    end

    self.winid = vim.api.nvim_get_current_win()

    if self.bufname then self.bufnr = vim.fn.bufnr(self.bufname) end

    if not self.bufnr or self.bufnr == -1 then self.bufnr = vim.api.nvim_create_buf(false, true) end

    vim.api.nvim_win_set_buf(self.winid, self.bufnr)

    if not self.enter then vim.api.nvim_set_current_win(current_bufnr) end
  elseif self.kind:match("^replace") then
    self.winid = vim.api.nvim_get_current_win()

    if self.bufname then self.bufnr = vim.fn.bufnr(self.bufname) end

    if not self.bufnr or self.bufnr == -1 then
      vim.api.nvim_command("enew")
      self.bufnr = vim.api.nvim_get_current_buf()
    else
      vim.api.nvim_win_set_buf(self.winid, self.bufnr)
    end
  else
    if self.bufname then self.bufnr = vim.fn.bufnr(self.bufname) end

    if not self.bufnr or self.bufnr == -1 then self.bufnr = vim.api.nvim_create_buf(false, true) end

    self.winid = vim.api.nvim_open_win(self.bufnr, self.enter, win_config)
  end

  if self.on_show then self.on_show() end

  self.augroup = vim.api.nvim_create_augroup("fyler_augroup_win_" .. self.bufnr, { clear = true })
  self.namespace = vim.api.nvim_create_namespace("fyler_namespace_win_" .. self.bufnr)

  local mappings_opts = self.mappings_opts or {}
  mappings_opts.buffer = self.bufnr
  for keys, v in pairs(self.mappings or {}) do
    for _, k in ipairs(util.tbl_wrap(keys)) do
      vim.keymap.set("n", k, v, mappings_opts)
    end
  end

  for k, v in pairs(self.user_mappings or {}) do
    vim.keymap.set("n", k, v, mappings_opts)
  end

  for option, value in pairs(self.win_opts or {}) do
    util.set_win_option(self.winid, option, value)
  end

  for option, value in pairs(self.buf_opts or {}) do
    util.set_buf_option(self.bufnr, option, value)
  end

  if self.bufname then vim.api.nvim_buf_set_name(self.bufnr, self.bufname) end

  for event, callback in pairs(self.autocmds or {}) do
    vim.api.nvim_create_autocmd(event, { group = self.augroup, buffer = self.bufnr, callback = callback })
  end

  for event, callback in pairs(self.user_autocmds or {}) do
    vim.api.nvim_create_autocmd("User", { pattern = event, group = self.augroup, callback = callback })
  end

  vim.api.nvim_buf_attach(self.bufnr, false, {
    on_detach = function()
      if self.autocmds or self.user_autocmds then pcall(vim.api.nvim_del_augroup_by_id, self.augroup) end
    end,
  })

  if self.render then self.render() end
end

function Win:hide()
  if self.kind:match("^replace") then
    local altbufnr = vim.fn.bufnr("#")
    if altbufnr == -1 or altbufnr == self.bufnr then
      util.try(vim.cmd.enew)
    else
      util.try(vim.api.nvim_win_set_buf, self.winid, altbufnr)
    end
  else
    util.try(vim.api.nvim_win_close, self.winid, true)
  end

  util.try(vim.api.nvim_buf_delete, self.bufnr, { force = true })

  if self.on_hide then self.on_hide() end
end

return Win


================================================
FILE: lua/fyler/log.lua
================================================
local M = {}

---@param message string
---@param level integer
local __log = vim.schedule_wrap(function(message, level) vim.notify(message, level, { title = " [Fyler.nvim] " }) end)

---@param message string
function M.info(message) __log(message, vim.log.levels.INFO) end

---@param message string
function M.warn(message) __log(message, vim.log.levels.WARN) end

---@param message string
function M.error(message) __log(message, vim.log.levels.ERROR) end

return M


================================================
FILE: lua/fyler/views/finder/actions.lua
================================================
local Path = require("fyler.lib.path")
local config = require("fyler.config")
local helper = require("fyler.views.finder.helper")

local M = {}

---@param self Finder
function M.n_close(self)
  return function() self:close() end
end

---@class fyler.views.finder.actions.select_opts
---@field winpick? boolean Whether to use winpick to select the file (default: true)

-- NOTE: Dependency injection due to shared logic between select actions
---@param self Finder
---@param opener fun(path: string)
---@param opts? fyler.views.finder.actions.select_opts
local function _select(self, opener, opts)
  opts = vim.tbl_extend("force", { winpick = true }, opts or {})

  local ref_id = helper.parse_ref_id(vim.api.nvim_get_current_line())
  if not ref_id then return end

  local entry = self.files:node_entry(ref_id)
  if not entry then return end

  if entry.type == "directory" then
    if entry.open then
      self.files:collapse_node(ref_id)
    else
      self.files:expand_node(ref_id)
    end

    return self:dispatch_refresh({ force_update = true })
  end

  -- Close if kind=replace|float or config.values.views.finder.close_on_select is enabled
  local should_close = self.win.kind:match("^replace")
    or self.win.kind:match("^float")
    or config.values.views.finder.close_on_select

  local function get_target_window()
    if vim.api.nvim_win_is_valid(self.win.origin_win) then return self.win.origin_win end

    for _, winid in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
      if vim.api.nvim_win_get_config(winid).relative == "" then
        self.win.origin_win = winid
        return winid
      end
    end
  end

  local function open_in_window(winid)
    winid = winid or get_target_window()
    assert(winid and vim.api.nvim_win_is_valid(winid), "Unexpected invalid window")

    if should_close then self:action_call("n_close") end

    vim.api.nvim_set_current_win(winid)

    opener(entry.path)
  end

  if opts.winpick then
    -- For split variants, we should pick windows
    config.winpick_provider({ self.win.winid }, open_in_window, config.winpick_opts)
  else
    open_in_window()
  end
end

function M.n_select_tab(self)
  return function()
    _select(
      self,
      function(path)
        vim.cmd.tabedit({
          args = { vim.fn.fnameescape(Path.new(path):os_path()) },
          mods = { keepalt = false },
        })
      end,
      { winpick = false }
    )
  end
end

function M.n_select_v_split(self)
  return function()
    _select(
      self,
      function(path)
        vim.cmd.vsplit({
          args = { vim.fn.fnameescape(Path.new(path):os_path()) },
          mods = { keepalt = false },
        })
      end
    )
  end
end

function M.n_select_split(self)
  return function()
    _select(
      self,
      function(path)
        vim.cmd.split({
          args = { vim.fn.fnameescape(Path.new(path):os_path()) },
          mods = { keepalt = false },
        })
      end
    )
  end
end

function M.n_select(self)
  return function()
    _select(
      self,
      function(path)
        vim.cmd.edit({
          args = { vim.fn.fnameescape(Path.new(path):os_path()) },
          mods = { keepalt = false },
        })
      end
    )
  end
end

---@param self Finder
function M.n_collapse_all(self)
  return function()
    self.files:collapse_all()
    self:dispatch_refresh({ force_update = true })
  end
end

---@param self Finder
function M.n_goto_parent(self)
  return function()
    local parent_dir = Path.new(self:getcwd()):parent():posix_path()
    if parent_dir == self:getcwd() then return end
    self:change_root(parent_dir):dispatch_refresh({ force_update = true })
  end
end

---@param self Finder
function M.n_goto_cwd(self)
  return function()
    if self:getrwd() == self:getcwd() then return end
    self:change_root(self:getrwd()):dispatch_refresh({ force_update = true })
  end
end

---@param self Finder
function M.n_goto_node(self)
  return function()
    local ref_id = helper.parse_ref_id(vim.api.nvim_get_current_line())
    if not ref_id then return end

    local entry = self.files:node_entry(ref_id)
    if not entry then return end

    if entry.type == "directory" then
      self:change_root(entry.path):dispatch_refresh({ force_update = true })
    else
      self:action_call("n_select")
    end
  end
end

---@param self Finder
function M.n_collapse_node(self)
  return function()
    local ref_id = helper.parse_ref_id(vim.api.nvim_get_current_line())
    if not ref_id then return end

    local entry = self.files:node_entry(ref_id)
    if not entry then return end

    -- should not collapse root, so get it's id
    local root_ref_id = self.files.trie.value
    if entry.type == "directory" and ref_id == root_ref_id then return end

    local collapse_target = self.files:find_parent(ref_id)
    if (not collapse_target) or (not entry.open) and collapse_target == root_ref_id then return end

    local focus_ref_id
    if entry.type == "directory" and entry.open then
      self.files:collapse_node(ref_id)
      focus_ref_id = ref_id
    else
      self.files:collapse_node(collapse_target)
      focus_ref_id = collapse_target
    end

    self:dispatch_refresh({
      onrender = function()
        if self:isopen() then vim.fn.search(string.format("/%05d", focus_ref_id)) end
      end,
    })
  end
end

return M


================================================
FILE: lua/fyler/views/finder/files/init.lua
================================================
local Path = require("fyler.lib.path")
local Trie = require("fyler.lib.structs.trie")
local fs = require("fyler.lib.fs")
local manager = require("fyler.views.finder.files.manager")
local util = require("fyler.lib.util")

---@class Files
---@field trie Trie
---@field root_path string
---@field finder Finder
local Files = {}
Files.__index = Files

---@param opts table
---@return Files
function Files.new(opts)
  assert(Path.new(opts.path):is_directory(), "Files root must be a directory")

  local instance = {
    root_path = opts.path,
    finder = opts.finder,
    trie = Trie.new(manager.set({
      name = opts.name,
      open = opts.open,
      path = opts.path,
      type = "directory",
      updated = false,
    })),
  }

  local root_entry = manager.get(instance.trie.value)
  if root_entry.open then instance.finder.watcher:start(root_entry.path) end

  setmetatable(instance, Files)

  return instance
end

---@param path string
---@return string[]|nil
function Files:path_to_segments(path)
  local posix_path = Path.new(path):posix_path()
  if not vim.startswith(posix_path, self.root_path) then return nil end

  local relative = posix_path:sub(#self.root_path + 1)
  if relative:sub(1, 1) == "/" then relative = relative:sub(2) end

  return util.filter_bl(vim.split(relative, "/"))
end

---@param ref_id integer
---@return FilesEntry
function Files:node_entry(ref_id) return manager.get(assert(ref_id, "cannot find node without ref_id")) end

---@param ref_id integer
---@return Trie|nil
function Files:find_node_by_ref_id(ref_id)
  local entry = manager.get(ref_id)
  local segments = self:path_to_segments(entry.link or entry.path)
  if not segments then return nil end
  return self.trie:find(segments)
end

---@param ref_id integer
function Files:expand_node(ref_id)
  local entry = manager.get(ref_id)
  assert(entry, "cannot locate entry with given ref_id")

  if entry.type ~= "directory" then return self end

  entry.open = true
  self.finder.watcher:start(entry.path)

  return self
end

---@param ref_id integer
function Files:collapse_node(ref_id)
  local entry = manager.get(ref_id)
  assert(entry, "cannot locate entry with given ref_id")

  if entry.type ~= "directory" then return self end

  entry.open = false
  self.finder.watcher:stop(entry.path)

  return self
end

---@param ref_id integer
---@return integer|nil
function Files:find_parent(ref_id)
  local entry = manager.get(ref_id)
  local segments = self:path_to_segments(entry.link or entry.path)

  if not segments or #segments == 0 then return nil end

  local parent_segments = {}
  for i = 1, #segments - 1 do
    parent_segments[i] = segments[i]
  end

  if #parent_segments == 0 then return self.trie.value end

  local parent_node = self.trie:find(parent_segments)
  return parent_node and parent_node.value or nil
end

function Files:collapse_all()
  for _, child in pairs(self.trie.children) do
    self:_collapse_recursive(child)
  end
end

---@param node Trie
function Files:_collapse_recursive(node)
  local entry = manager.get(node.value)

  if entry.type == "directory" and entry.open then
    entry.open = false
    self.finder.watcher:stop(entry.path)
  end

  for _, child in pairs(node.children) do
    self:_collapse_recursive(child)
  end
end

---@param parent_ref_id integer
---@param opts FilesEntryOpts
function Files:add_child(parent_ref_id, opts)
  local parent_entry = manager.get(parent_ref_id)

  opts.path = Path.new(parent_entry.link or parent_entry.path):join(opts.name):posix_path()

  local child_ref_id = manager.set(opts)

  local parent_segments = self:path_to_segments(parent_entry.link or parent_entry.path)
  local parent_node = self.trie:find(parent_segments or {})

  if parent_node then parent_node.children[opts.name] = Trie.new(child_ref_id) end
end

---@param ... integer|function
function Files:update(...)
  local ref_id = nil
  local onupdate = nil

  for i = 1, select("#", ...) do
    local arg = select(i, ...)

    if type(arg) == "number" then
      ref_id = arg
    elseif type(arg) == "function" then
      onupdate = arg
    end
  end

  if not onupdate then error("callback function is required") end

  local node = ref_id and self:find_node_by_ref_id(ref_id) or self.trie

  self:_update(node, function(err)
    if err then return onupdate(err) end

    onupdate(nil, self)
  end)
end

---@param node Trie
---@param onupdate function
function Files:_update(node, onupdate)
  local node_entry = manager.get(node.value)
  if not node_entry.open then return onupdate(nil) end

  fs.ls({
    path = Path.new(node_entry.link or node_entry.path):os_path(),
  }, function(err, entries)
    if err or not entries then return onupdate(err) end

    local entry_paths = {}
    for _, entry in ipairs(entries) do
      entry_paths[entry.name] = entry
    end

    for name, child_node in pairs(node.children) do
      if not entry_paths[name] then
        local child_entry = manager.get(child_node.value)
        if child_entry.type == "directory" then self.finder.watcher:stop(child_entry.path) end
        node.children[name] = nil
      end
    end

    for name, entry in pairs(entry_paths) do
      if not node.children[name] then
        local child_ref_id = manager.set(entry)
        local child_node = Trie.new(child_ref_id)
        node.children[name] = child_node

        local child_entry = manager.get(child_ref_id)
        if child_entry.type == "directory" and child_entry.open then self.finder.watcher:start(child_entry.path) end
      end
    end

    node_entry.updated = true

    local children_list = {}
    for _, child in pairs(node.children) do
      table.insert(children_list, child)
    end

    local function update_next(index)
      if index > #children_list then return onupdate(nil) end

      self:_update(children_list[index], function(err)
        if err then return onupdate(err) end
        update_next(index + 1)
      end)
    end

    update_next(1)
  end)
end

---@param path string
---@param onnavigate function
function Files:navigate(path, onnavigate)
  local segments = self:path_to_segments(path)
  if not segments then return onnavigate(nil, nil, false) end

  if #segments == 0 then return onnavigate(nil, self.trie.value, false) end

  local did_update = false

  local function process_segment(index, current_node)
    if index > #segments then return onnavigate(nil, current_node.value, did_update) end

    local segment = segments[index]
    local current_entry = manager.get(current_node.value)

    if current_entry.type == "directory" then
      local needs_update = not current_entry.open or not current_entry.updated

      if needs_update then
        did_update = true
        self:expand_node(current_node.value):update(current_node.value, function(err)
          if err then return onnavigate(err, nil, did_update) end

          local next_node = current_node.children[segment]
          if not next_node then return onnavigate(nil, nil, did_update) end

          process_segment(index + 1, next_node)
        end)
      else
        local next_node = current_node.children[segment]
        if not next_node then return onnavigate(nil, nil, did_update) end

        process_segment(index + 1, next_node)
      end
    else
      return onnavigate(nil, nil, did_update)
    end
  end

  process_segment(1, self.trie)
end

---@return table
function Files:totable() return self:_totable(self.trie) end

---@param node Trie
---@return table
function Files:_totable(node)
  local entry = manager.get(node.value)
  local table_node = {
    link = entry.link,
    name = entry.name,
    open = entry.open,
    path = entry.path,
    ref_id = node.value,
    type = entry.type,
    children = {},
  }

  if not entry.open then return table_node end

  local child_list = {}
  for name, child in pairs(node.children) do
    local child_entry = manager.get(child.value)
    table.insert(child_list, { name = name, node = child, is_dir = child_entry.type == "directory" })
  end

  for _, item in ipairs(child_list) do
    table.insert(table_node.children, self:_totable(item.node))
  end

  return table_node
end

---@return table[]
function Files:diff_with_buffer() return require("fyler.views.finder.files.resolver").new(self):resolve() end

return Files


================================================
FILE: lua/fyler/views/finder/files/manager.lua
================================================
local util = require("fyler.lib.util")

---@class FilesEntry
---@field ref_id integer
---@field open boolean
---@field updated boolean
---@field name string
---@field path string
---@field type string
---@field link string|nil
local Entry = {}
Entry.__index = Entry

---@class FilesEntryOpts
---@field ref_id integer|nil
---@field open boolean|nil
---@field updated boolean|nil
---@field name string|nil
---@field path string
---@field type string|nil
---@field link string|nil

local M = {}

local Entries = {}
local PathToRefId = {} -- entry path (including symlink path) -> ref_id
local ResolvedPathToRefId = {} -- resolved path -> ref_id (for follow_current_file on symlinks)
local NextRefId = 1

local DEFAULT_ENTRY = {
  open = false,
  updated = false,
  type = "file",
}

---@param ref_id integer
---@return FilesEntry
function M.get(ref_id)
  assert(ref_id, "cannot find entry without ref_id")
  local entry = Entries[ref_id]
  assert(entry, "cannot locate entry with given ref_id")
  return entry
end

---@param opts FilesEntryOpts
---@return integer
function M.set(opts)
  assert(opts and opts.path, "FilesEntry requires at least a path")

  local path = opts.link or opts.path
  local ref_id = PathToRefId[path]
  local entry = ref_id and Entries[ref_id]

  if entry then
    Entries[ref_id] = util.tbl_merge_force(entry, opts)
    if opts.link then ResolvedPathToRefId[opts.path] = ref_id end
    return ref_id
  end

  ref_id = NextRefId
  NextRefId = NextRefId + 1

  local new_entry = util.tbl_merge_force({}, DEFAULT_ENTRY)
  new_entry = util.tbl_merge_force(new_entry, opts)
  new_entry.ref_id = ref_id

  PathToRefId[path] = ref_id
  if opts.link then ResolvedPathToRefId[opts.path] = ref_id end
  Entries[ref_id] = new_entry

  return ref_id
end

---@param resolved_path string
---@return string|nil
function M.find_link_path_from_resolved(resolved_path)
  local ref_id = ResolvedPathToRefId[resolved_path]
  if ref_id then
    local entry = Entries[ref_id]
    if entry and entry.link then return entry.link end
  end

  local parent = resolved_path
  while parent do
    parent = parent:match("^(.*)/[^/]+$")
    if not parent or parent == "/" then break end

    ref_id = ResolvedPathToRefId[parent]
    if ref_id then
      local entry = Entries[ref_id]
      if entry and entry.link then return entry.link .. resolved_path:sub(#parent + 1) end
    end
  end
end
return M


================================================
FILE: lua/fyler/views/finder/files/resolver.lua
================================================
local Path = require("fyler.lib.path")
local helper = require("fyler.views.finder.helper")
local manager = require("fyler.views.finder.files.manager")
local util = require("fyler.lib.util")

local Resolver = {}
Resolver.__index = Resolver

function Resolver.new(files) return setmetatable({ files = files }, Resolver) end

function Resolver:resolve()
  local parsed_tree = self:_parse_buffer()
  local actions = self:_generate_actions(parsed_tree)
  actions = self:_filter_actions(actions)
  actions = self:_topsort_actions(actions)

  return actions
end

---@private
function Resolver:_parse_buffer()
  local root_entry = manager.get(self.files.trie.value)
  assert(root_entry, "Failed to get root entry from trie")

  local parsed_tree = {
    ref_id = root_entry.ref_id,
    path = root_entry.path,
    children = {},
  }

  local parent_stack = require("fyler.lib.structs.stack").new()
  parent_stack:push({ node = parsed_tree, indent = -1 })

  local buffer_lines = vim.api.nvim_buf_get_lines(self.files.finder.win.bufnr, 0, -1, false)

  for _, line in ipairs(util.filter_bl(buffer_lines)) do
    local entry_name = helper.parse_name(line)
    local entry_ref_id = helper.parse_ref_id(line)
    local entry_indent = helper.parse_indent_level(line)

    while parent_stack:size() > 1 and parent_stack:top().indent >= entry_indent do
      parent_stack:pop()
    end

    local current_parent = parent_stack:top()
    local parent_entry = manager.get(current_parent.node.ref_id)
    local parent_path = parent_entry.link or parent_entry.path

    local child_node = {
      ref_id = entry_ref_id,
      path = Path.new(parent_path):join(entry_name):posix_path(),
    }

    current_parent.node.type = "directory"
    current_parent.node.children = current_parent.node.children or {}

    table.insert(current_parent.node.children, child_node)

    parent_stack:push({ node = child_node, indent = entry_indent })
  end

  return parsed_tree
end

---@private
function Resolver:_generate_actions(parsed_tree)
  local old_ref = {}
  local new_ref = {}
  local actions = {}

  local function traverse(node, should_continue)
    if should_continue(node) then
      for _, child_node in pairs(node.children or {}) do
        traverse(child_node, should_continue)
      end
    end
  end

  traverse(self.files.trie, function(node)
    local node_entry = assert(manager.get(node.value), "Unexpected nil node entry")

    if node_entry.link then
      old_ref[node.value] = node_entry.link
    else
      old_ref[node.value] = assert(node_entry.path, "Unexpected nil node entry path")
    end

    return node_entry.open
  end)

  traverse(parsed_tree, function(node)
    if not node.ref_id then
      table.insert(actions, { type = "create", path = node.path })
    else
      new_ref[node.ref_id] = new_ref[node.ref_id] or {}
      table.insert(new_ref[node.ref_id], node.path)
    end
    return true
  end)

  local function insert_action(ref_id, old_path)
    local dst_paths = new_ref[ref_id]

    if not dst_paths then
      table.insert(actions, { type = "delete", path = old_path })
      return
    end

    if #dst_paths == 1 then
      if dst_paths[1] ~= old_path then table.insert(actions, { type = "move", src = old_path, dst = dst_paths[1] }) end
      return
    end

    if util.if_any(dst_paths, function(path) return path == old_path end) then
      util.tbl_each(dst_paths, function(path)
        if path ~= old_path then table.insert(actions, { type = "copy", src = old_path, dst = path }) end
      end)
    else
      table.insert(actions, { type = "move", src = old_path, dst = dst_paths[1] })
      for i = 2, #dst_paths do
        table.insert(actions, { type = "copy", src = dst_paths[1], dst = dst_paths[i] })
      end
    end
  end

  for ref_id, original_path in pairs(old_ref) do
    insert_action(ref_id, original_path)
  end

  return actions
end

---@private
function Resolver:_filter_actions(actions)
  local seen_actions = {}

  local function create_action_key(action)
    if vim.list_contains({ "create", "delete" }, action.type) then
      return action.type .. ":" .. action.path
    elseif vim.list_contains({ "move", "copy" }, action.type) then
      return action.type .. ":" .. action.src .. "," .. action.dst
    else
      error(string.format("Unexpected action type: %s", action.type))
    end
  end

  return util.tbl_filter(actions, function(action)
    local action_key = create_action_key(action)

    if seen_actions[action_key] then
      return false
    else
      seen_actions[action_key] = true
    end

    if action.type == "move" or action.type == "copy" then return action.src ~= action.dst end

    return true
  end)
end

---@private
function Resolver:_topsort_actions(actions)
  if #actions == 0 then return actions end

  local Path = require("fyler.lib.path")
  local Trie = require("fyler.lib.structs.trie")

  local temp_paths = {}

  local function build_conflict_graph(action_list)
    local conflicts = {}

    for i = 1, #action_list do
      conflicts[i] = {}
    end

    for i = 1, #action_list do
      local action_i = action_list[i]

      for j = i + 1, #action_list do
        local action_j = action_list[j]

        if action_i.type == "move" and action_j.type == "move" then
          if action_i.src == action_j.dst and action_i.dst == action_j.src then
            table.insert(conflicts[i], j)
            table.insert(conflicts[j], i)
          end
        end
      end
    end

    return conflicts
  end

  local function expand_swaps(action_list)
    local conflicts = build_conflict_graph(action_list)
    local expanded = {}
    local processed = {}

    for i = 1, #action_list do
      if not processed[i] then
        local action = action_list[i]

        local swap_partner = nil
        for _, j in ipairs(conflicts[i]) do
          if not processed[j] then
            swap_partner = j
            break
          end
        end

        if swap_partner then
          local action_a = action_list[i]
          local action_b = action_list[swap_partner]
          local tmp_path = string.format("%s_temp_%05d", action_a.src, math.random(99999))

          temp_paths[tmp_path] = true

          table.insert(expanded, { type = "move", src = action_a.src, dst = tmp_path })
          table.insert(expanded, { type = "move", src = action_b.src, dst = action_b.dst })
          table.insert(expanded, { type = "move", src = tmp_path, dst = action_a.dst })

          processed[i] = true
          processed[swap_partner] = true
        else
          table.insert(expanded, action)
          processed[i] = true
        end
      end
    end

    return expanded
  end

  actions = expand_swaps(actions)

  local function build_dependency_graph(action_list)
    local src_trie = Trie.new()
    local dst_trie = Trie.new()

    local action_to_index = {}
    for i, action in ipairs(action_list) do
      action_to_index[action] = i
    end

    for _, action in ipairs(action_list) do
      local function append(existing_actions) return vim.list_extend(existing_actions or {}, { action }) end

      if action.type == "create" then
        dst_trie:insert(Path.new(action.path):segments(), append)
      elseif action.type == "delete" then
        src_trie:insert(Path.new(action.path):segments(), append)
      else
        src_trie:insert(Path.new(action.src):segments(), append)
        dst_trie:insert(Path.new(action.dst):segments(), append)
      end
    end

    local function collect_parent_actions(trie, target_path, collected_actions)
      local path_segments = Path.new(target_path):segments()
      local ancestor_nodes = { trie }

      for _, segment in ipairs(path_segments) do
        local current_node = ancestor_nodes[#ancestor_nodes]
        if not current_node or not current_node.children then return end

        local child_node = current_node.children[segment]
        if not child_node then return end

        table.insert(ancestor_nodes, child_node)
      end

      table.remove(ancestor_nodes)

      while #ancestor_nodes > 0 do
        local ancestor = ancestor_nodes[#ancestor_nodes]
        if ancestor.value and #ancestor.value > 0 then
          vim.list_extend(collected_actions, ancestor.value)
          break
        end

        table.remove(ancestor_nodes)
      end
    end

    local function collect_descendant_actions(trie, target_path, collected_actions, action_filter)
      local path_segments = Path.new(target_path):segments()
      local target_node = trie:find(path_segments)

      if not target_node then return end

      for _, child_node in pairs(target_node.children) do
        child_node:dfs(function(node)
          if node.value then
            if action_filter then
              for _, action in ipairs(node.value) do
                if action_filter(action) then table.insert(collected_actions, action) end
              end
            else
              vim.list_extend(collected_actions, node.value)
            end
          end
        end)
      end
    end

    local function collect_actions_at_path(trie, target_path, collected_actions, action_filter)
      local path_segments = Path.new(target_path):segments()
      local target_node = trie:find(path_segments)

      if target_node and target_node.value then
        for _, action in ipairs(target_node.value) do
          if not action_filter or action_filter(action) then table.insert(collected_actions, action) end
        end
      end
    end

    local function action_dependencies(action_index)
      local action = action_list[action_index]
      local function is_destructive_action(op) return op.type == "move" or op.type == "delete" end

      local dependencies = {}

      if action.type == "delete" then
        collect_descendant_actions(src_trie, action.path, dependencies)
      elseif action.type == "create" then
        collect_parent_actions(dst_trie, action.path, dependencies)
        if not temp_paths[action.path] then
          collect_actions_at_path(src_trie, action.path, dependencies, is_destructive_action)
        end
      elseif action.type == "move" then
        collect_parent_actions(dst_trie, action.dst, dependencies)
        collect_descendant_actions(src_trie, action.src, dependencies)
        if not temp_paths[action.dst] then
          collect_actions_at_path(src_trie, action.dst, dependencies, is_destructive_action)
        end
      elseif action.type == "copy" then
        collect_parent_actions(dst_trie, action.dst, dependencies)
        if not temp_paths[action.dst] then
          collect_actions_at_path(src_trie, action.dst, dependencies, is_destructive_action)
        end
      end

      local filtered_deps = {}
      for _, dep in ipairs(dependencies) do
        local dep_index = action_to_index[dep]
        if dep_index and dep_index ~= action_index then table.insert(filtered_deps, dep) end
      end

      return filtered_deps
    end

    local indegree = {}
    local graph = {}

    for i = 1, #action_list do
      indegree[i] = 0
      graph[i] = {}
    end

    for i = 1, #action_list do
      local dependencies = action_dependencies(i)
      for _, dep in ipairs(dependencies) do
        local dep_index = action_to_index[dep]
        if dep_index then
          table.insert(graph[dep_index], i)
          indegree[i] = indegree[i] + 1
        end
      end
    end

    return graph, indegree, action_list
  end

  local graph, indegree, final_actions = build_dependency_graph(actions)

  local ready_actions = {}
  for i = 1, #final_actions do
    if indegree[i] == 0 then table.insert(ready_actions, i) end
  end

  local topsorted = {}
  while #ready_actions > 0 do
    local current_index = table.remove(ready_actions, 1)
    table.insert(topsorted, final_actions[current_index])

    for _, dependent_index in ipairs(graph[current_index]) do
      indegree[dependent_index] = indegree[dependent_index] - 1
      if indegree[dependent_index] == 0 then table.insert(ready_actions, dependent_index) end
    end
  end

  if #topsorted ~= #final_actions then
    error(string.format("Circular dependency detected in actions: sorted %d of %d actions", #topsorted, #final_actions))
  end

  return topsorted
end

return Resolver


================================================
FILE: lua/fyler/views/finder/helper.lua
================================================
local M = {}

---@param uri string|nil
---@return boolean
function M.is_protocol_uri(uri) return uri and (not not uri:match("^fyler://%d+")) or false end

---@param dir string
---@param tab string|integer
---@return string
function M.build_protocol_uri(tab, dir) return string.format("fyler://%s//%s", tostring(tab), dir) end

---@param uri string|nil
---@return string
function M.normalize_uri(uri)
  local dir, tab = nil, nil
  if not uri or uri == "" then
    dir = vim.fn.getcwd()
    tab = tostring(vim.api.nvim_get_current_tabpage())
  elseif M.is_protocol_uri(uri) then
    tab, dir = M.parse_protocol_uri(uri)
    dir = dir or vim.fn.getcwd()
    tab = tab or tostring(vim.api.nvim_get_current_tabpage())
  else
    dir = uri
    tab = tostring(vim.api.nvim_get_current_tabpage())
  end
  return M.build_protocol_uri(tab, require("fyler.lib.path").new(dir):posix_path())
end

---@param uri string
---@return string|nil, string|nil
function M.parse_protocol_uri(uri)
  if M.is_protocol_uri(uri) then return string.match(uri, "^fyler://(%d+)//(.*)$") end
end

---@param str string
---@return integer|nil
function M.parse_ref_id(str) return tonumber(str:match("/(%d+)")) end

---@param str string
---@return integer
function M.parse_indent_level(str) return #(str:match("^(%s*)" or "")) end

---@param str string
---@return string
function M.parse_name(str)
  if M.parse_ref_id(str) then
    return str:match("/%d+ (.*)$")
  else
    return str:gsub("^%s*", ""):match(".*")
  end
end

return M


================================================
FILE: lua/fyler/views/finder/indent.lua
================================================
local config = require("fyler.config")

local M = {}

local INDENT_WIDTH = 2
local snapshots = {}

---@param winid integer
---@param bufnr integer
---@return string
local function make_key(winid, bufnr) return winid .. "_" .. bufnr end

---@param text string
---@return boolean
local function only_spaces_or_tabs(text)
  for i = 1, #text do
    local byte = string.byte(text, i)
    if byte ~= 32 and byte ~= 9 then return false end
  end
  return true
end

---@param line string
---@return integer
local function calculate_indent(line)
  local indent = 0
  for i = 1, #line do
    local byte = string.byte(line, i)
    if byte == 32 then
      indent = indent + 1
    elseif byte == 9 then
      indent = indent + 8
    else
      break
    end
  end
  return indent
end

---@param bufnr integer
---@param lnum integer
---@param snapshot table
---@return integer indent
local function compute_indent(bufnr, lnum, snapshot)
  local cached = snapshot[lnum]
  if cached then return cached end

  local ok, lines = pcall(vim.api.nvim_buf_get_lines, bufnr, lnum - 1, lnum, false)
  if not ok or not lines or #lines == 0 then return 0 end

  local line = lines[1]
  local is_empty = #line == 0 or only_spaces_or_tabs(line)

  local indent
  if is_empty then
    indent = 0
    local prev_lnum = lnum - 1
    while prev_lnum >= 1 do
      local prev_indent = snapshot[prev_lnum] or compute_indent(bufnr, prev_lnum, snapshot)
      if prev_indent > 0 then
        indent = prev_indent
        break
      end
      prev_lnum = prev_lnum - 1
    end
  else
    indent = calculate_indent(line)
  end

  snapshot[lnum] = indent
  return indent
end

---@param snapshot table
---@param lnum integer
---@return integer|nil
local function next_non_empty_indent(snapshot, lnum)
  local next_lnum = lnum + 1
  while true do
    local next_indent = snapshot[next_lnum]
    if next_indent == nil then return nil end
    if next_indent > 0 then return next_indent end
    next_lnum = next_lnum + 1
  end
end

local function setup_provider()
  if M.indent_ns then return end

  M.indent_ns = vim.api.nvim_create_namespace("fyler_indentscope")

  vim.api.nvim_set_decoration_provider(M.indent_ns, {
    on_start = function()
      if not config.values.views.finder.indentscope.enabled then return false end

      for key in pairs(M.windows) do
        snapshots[key] = {}
      end
      return true
    end,

    on_win = function(_, winid, bufnr, topline, botline)
      local key = make_key(winid, bufnr)
      local win = M.windows[key]
      if not win or not win:has_valid_bufnr() or win.winid ~= winid or win.bufnr ~= bufnr then return false end

      local snapshot = snapshots[key] or {}
      snapshots[key] = snapshot

      for lnum = topline + 1, botline + 1 do
        compute_indent(bufnr, lnum, snapshot)
      end

      return true
    end,

    on_line = function(_, winid, bufnr, row)
      local key = make_key(winid, bufnr)
      local win = M.windows[key]
      if not win or not win:has_valid_bufnr() or win.winid ~= winid or win.bufnr ~= bufnr then return end

      local snapshot = snapshots[key]
      if not snapshot then return end

      local lnum = row + 1
      local indent = snapshot[lnum]
      if not indent or indent < INDENT_WIDTH then return end

      local next_indent = next_non_empty_indent(snapshot, lnum)
      local markers = config.values.views.finder.indentscope.markers

      for col = 0, indent - INDENT_WIDTH, INDENT_WIDTH do
        local scope_level = col + INDENT_WIDTH
        local is_scope_end = not next_indent or next_indent < scope_level

        local marker = is_scope_end and markers[2] or markers[1]

        vim.api.nvim_buf_set_extmark(bufnr, M.indent_ns, row, col, {
          virt_text = { marker },
          virt_text_pos = "overlay",
          hl_mode = "combine",
          ephemeral = true,
          priority = 10,
        })
      end
    end,
  })
end

---@param win Win
function M.attach(win)
  if not config.values.views.finder.indentscope.enabled then return end

  setup_provider()

  if not M.windows then M.windows = {} end

  local key = make_key(win.winid, win.bufnr)
  M.windows[key] = win
end

---@param win Win
function M.detach(win)
  if not M.windows then return end

  local key = make_key(win.winid, win.bufnr)
  M.windows[key] = nil
  snapshots[key] = nil

  if next(M.windows) == nil then
    M.windows = {}
    snapshots = {}
  end
end

function M.enable(win) M.attach(win) end

function M.disable()
  M.windows = {}
  snapshots = {}
end

return M


================================================
FILE: lua/fyler/views/finder/init.lua
================================================
local Path = require("fyler.lib.path")
local async = require("fyler.lib.async")
local config = require("fyler.config")
local helper = require("fyler.views.finder.helper")
local manager = require("fyler.views.finder.files.manager")
local util = require("fyler.lib.util")

local M = {}

---@class Finder
---@field uri string
---@field files Files
---@field watcher Watcher
local Finder = {}
Finder.__index = Finder

function Finder.new(uri) return setmetatable({ uri = uri }, Finder) end

---@param name string
function Finder:action(name)
  local action = require("fyler.views.finder.actions")[name]
  return assert(action, string.format("action %s is not available", name))(self)
end

---@param user_mappings table<string, function>
---@return table<string, function>
function Finder:action_wrap(user_mappings)
  local actions = {}
  for keys, fn in pairs(user_mappings) do
    actions[keys] = function() fn(self) end
  end
  return actions
end

---@param name string
---@param ... any
function Finder:action_call(name, ...) self:action(name)(...) end

---@deprecated
function Finder:exec_action(...)
  vim.notify("'exec_action' is deprecated use 'action_call'")
  self:action_call(...)
end

---@param kind WinKind|nil
function Finder:isopen(kind)
  return self.win
    and (kind and (self.win.kind == kind) or true)
    and self.win:has_valid_winid()
    and self.win:has_valid_bufnr()
end

---@param kind WinKind
function Finder:open(kind)
  local indent = require("fyler.views.finder.indent")

  local rev_maps = config.rev_maps("finder")
  local usr_maps = config.usr_maps("finder")
  local view_cfg = config.view_cfg("finder", kind)

  -- stylua: ignore start
  self.win = require("fyler.lib.win").new {
    autocmds      = {
      ["BufReadCmd"] = function()
        self:dispatch_refresh()
      end,
      ["BufWriteCmd"] = function()
        self:dispatch_mutation()
      end,
      [{"CursorMoved","CursorMovedI"}] = function()
        local cur = vim.api.nvim_get_current_line()
        local ref_id = helper.parse_ref_id(cur)
        if not ref_id then return end

        local _, ub = string.find(cur, ref_id)
        if not self.win:has_valid_winid() then return end

        local row, col = self.win:get_cursor()
        if not (row and col) then return end

        if col <= ub then self.win:set_cursor(row, ub + 1) end
      end,
    },
    border        = view_cfg.win.border,
    bufname       = self.uri,
    bottom        = view_cfg.win.bottom,
    buf_opts      = view_cfg.win.buf_opts,
    enter         = true,
    footer        = view_cfg.win.footer,
    footer_pos    = view_cfg.win.footer_pos,
    height        = view_cfg.win.height,
    kind          = kind,
    left          = view_cfg.win.left,
    mappings      = {
      [rev_maps["CloseView"]]    = self:action "n_close",
      [rev_maps["CollapseAll"]]  = self:action "n_collapse_all",
      [rev_maps["CollapseNode"]] = self:action "n_collapse_node",
      [rev_maps["GotoCwd"]]      = self:action "n_goto_cwd",
      [rev_maps["GotoNode"]]     = self:action "n_goto_node",
      [rev_maps["GotoParent"]]   = self:action "n_goto_parent",
      [rev_maps["Select"]]       = self:action "n_select",
      [rev_maps["SelectSplit"]]  = self:action "n_select_split",
      [rev_maps["SelectTab"]]    = self:action "n_select_tab",
      [rev_maps["SelectVSplit"]] = self:action "n_select_v_split",
    },
    mappings_opts = view_cfg.mappings_opts,
    on_show       = function()
      self.watcher:enable()
      indent.attach(self.win)
    end,
    on_hide       = function()
      self.watcher:disable()
      indent.detach(self.win)
    end,
    render        = function()
      if not config.values.views.finder.follow_current_file then
        return self:dispatch_refresh({ force_update = true })
      end

      local bufname = vim.fn.bufname("#")
      if bufname == "" then
        return self:dispatch_refresh({ force_update = true })
      end

      if helper.is_protocol_uri(bufname) then
        return self:dispatch_refresh({ force_update = true })
      end

      return M.navigate( bufname, { filter = { self.win.bufname }, force_update = true })
    end,
    right         = view_cfg.win.right,
    title         = string.format("%s", self:getcwd()),
    title_pos     = view_cfg.win.title_pos,
    top           = view_cfg.win.top,
    user_autocmds = {
      ["DispatchRefresh"] = function()
        self:dispatch_refresh({ force_update = true })
      end,
    },
    user_mappings = self:action_wrap(usr_maps),
    width         = view_cfg.win.width,
    win_opts      = view_cfg.win.win_opts,
  }
  -- stylua: ignore end

  self.win:show()
end

---@return string
function Finder:getrwd() return util.select_n(2, helper.parse_protocol_uri(self.uri)) end

---@return string
function Finder:getcwd() return Path.new(assert(self.files, "files is required").root_path):os_path() end

function Finder:cursor_node_entry()
  local entry
  vim.api.nvim_win_call(self.win.winid, function()
    local ref_id = helper.parse_ref_id(vim.api.nvim_get_current_line())
    if ref_id then entry = vim.deepcopy(self.files:node_entry(ref_id)) end
  end)
  return entry
end

function Finder:close()
  if self.win then self.win:hide() end
end

function Finder:navigate(...) self.files:navigate(...) end

-- Change `self.files` instance to provided directory path
---@param path string
function Finder:change_root(path)
  assert(path, "cannot change directory without path")
  assert(Path.new(path):is_directory(), "cannot change to non-directory path")

  self.watcher:disable(true)
  self.files = require("fyler.views.finder.files").new({
    open = true,
    name = Path.new(path):basename(),
    path = Path.new(path):posix_path(),
    finder = self,
  })

  if self.win then self.win:update_title(string.format(" %s ", path)) end

  return self
end

---@param opts { force_update: boolean, onrender: function }|nil
function Finder:dispatch_refresh(opts)
  opts = opts or {}

  -- Smart file system calculation, Use cache if not `opts.update` mentioned
  local get_table = async.wrap(function(onupdate)
    if opts.force_update then
      return self.files:update(function(_, this) onupdate(this:totable()) end)
    end

    return onupdate(self.files:totable())
  end)

  async.void(function()
    local files_table = get_table()
    vim.schedule(function()
      require("fyler.views.finder.ui").files(
        files_table,
        function(component, options) self.win.ui:render(component, options, opts.onrender) end
      )
    end)
  end)
end

local function run_mutation(operations)
  local async_handler = async.wrap(function(operation, _next)
    if config.values.views.finder.delete_to_trash and operation.type == "delete" then operation.type = "trash" end

    assert(require("fyler.lib.fs")[operation.type], "Unknown operation")(operation, _next)

    return operation.path or operation.dst
  end)

  local mutation_text_format = "Mutating (%d/%d)"
  local spinner = require("fyler.lib.spinner").new(string.format(mutation_text_format, 0, #operations))
  local last_focusable_operation = nil

  spinner:start()

  for i, operation in ipairs(operations) do
    local err = async_handler(operation)
    if err then
      vim.schedule_wrap(vim.notify)(err, vim.log.levels.ERROR, { title = "Fyler" })
    else
      last_focusable_operation = (operation.path or operation.dst) or last_focusable_operation
    end

    spinner:set_text(string.format(mutation_text_format, i, #operations))
  end

  spinner:stop()

  return last_focusable_operation
end

---@return boolean
local function can_skip_confirmation(operations)
  local count = { create = 0, delete = 0, move = 0, copy = 0 }

  util.tbl_each(operations, function(o) count[o.type] = (count[o.type] or 0) + 1 end)

  return count.create <= 5 and count.move <= 1 and count.copy <= 1 and count.delete <= 0
end

local get_confirmation = async.wrap(vim.schedule_wrap(function(...) require("fyler.input").confirm.open(...) end))

local function should_mutate(operations, cwd)
  if config.values.views.finder.confirm_simple and can_skip_confirmation(operations) then return true end

  return get_confirmation(require("fyler.views.finder.ui").operations(util.tbl_map(operations, function(operation)
    local result = vim.deepcopy(operation)
    if operation.type == "create" or operation.type == "delete" then
      result.path = cwd:relative(operation.path) or operation.path
    else
      result.src = cwd:relative(operation.src) or operation.src
      result.dst = cwd:relative(operation.dst) or operation.dst
    end
    return result
  end)))
end

function Finder:dispatch_mutation()
  async.void(function()
    local operations = self.files:diff_with_buffer()

    if vim.tbl_isempty(operations) then return self:dispatch_refresh() end

    if should_mutate(operations, require("fyler.lib.path").new(self:getcwd())) then
      M.navigate(run_mutation(operations), { force_update = true })
    end
  end)
end

local instances = {}

---@param uri string|nil
---@return Finder
function M.instance(uri)
  uri = assert(helper.normalize_uri(uri), "Faulty URI")

  local finder = instances[uri]
  if finder then return finder end

  local _, path = helper.parse_protocol_uri(uri) --[[@as string]]
  assert(Path.new(path):is_directory(), "Path is not a valid directory")

  finder = Finder.new(uri)
  finder.watcher = require("fyler.views.finder.watcher").new(finder)
  finder.files = require("fyler.views.finder.files").new({
    open = true,
    name = Path.new(path):basename(),
    path = Path.new(path):posix_path(),
    finder = finder,
  })

  instances[uri] = finder
  return instances[uri]
end

---@param fn fun(uri: string)
function M.each_finder(fn) util.tbl_each(instances, fn) end

---@param uri string|nil
---@param kind WinKind|nil
function M.open(uri, kind) M.instance(uri):open(kind or config.values.views.finder.win.kind) end

local function _select(opts, handler)
  if opts.filter then
    util.tbl_each(opts.filter, function(uri)
      if helper.is_protocol_uri(uri) then handler(uri) end
    end)
  else
    M.each_finder(handler)
  end
end

M.close = vim.schedule_wrap(function(opts)
  _select(opts or {}, function(uri) M.instance(uri):close() end)
end)

---@param uri string|nil
---@param kind WinKind|nil
M.toggle = vim.schedule_wrap(function(uri, kind)
  local finder = M.instance(uri)
  if finder:isopen(kind) then
    finder:close()
  else
    finder:open(kind or config.values.views.finder.win.kind)
  end
end)

M.focus = vim.schedule_wrap(function(opts)
  _select(opts or {}, function(uri) M.instance(uri).win:focus() end)
end)

-- TODO: Can futher optimize by determining whether `files:navgiate` did any change or not?
---@param path string|nil
M.navigate = vim.schedule_wrap(function(path, opts)
  opts = opts or {}

  local set_cursor = vim.schedule_wrap(function(finder, ref_id)
    if finder:isopen() and ref_id then
      vim.api.nvim_win_call(finder.win.winid, function() vim.fn.search(string.format("/%05d ", ref_id)) end)
    end
  end)

  _select(opts, function(uri)
    local finder = M.instance(uri)
    if not finder:isopen() then return end

    local update_table = async.wrap(function(...) finder.files:update(...) end)

    local navigate_path = async.wrap(function(...) finder:navigate(...) end)

    async.void(function()
      if opts.force_update then update_table() end

      local ref_id
      if path then
        local path = vim.fn.fnamemodify(Path.new(path):posix_path(), ":p")
        ref_id = util.select_n(2, navigate_path(path))

        if not ref_id then
          local link = manager.find_link_path_from_resolved(path)
          if link then ref_id = util.select_n(2, navigate_path(link)) end
        end
      end

      opts.onrender = function() set_cursor(finder, ref_id) end

      finder:dispatch_refresh(opts)
    end)
  end)
end)

return M


================================================
FILE: lua/fyler/views/finder/ui.lua
================================================
local Path = require("fyler.lib.path")
local Ui = require("fyler.lib.ui")
local config = require("fyler.config")
local diagnostic = require("fyler.lib.diagnostic")
local git = require("fyler.lib.git")
local util = require("fyler.lib.util")

local Component = Ui.Component
local Text = Ui.Text
local Row = Ui.Row
local Column = Ui.Column

local COLUMN_ORDER = config.values.views.finder.columns_order

local function sort_nodes(nodes)
  table.sort(nodes, function(x, y)
    local x_is_dir = x.type == "directory"
    local y_is_dir = y.type == "directory"
    if x_is_dir and not y_is_dir then
      return true
    elseif not x_is_dir and y_is_dir then
      return false
    else
      local function pad_numbers(str)
        return str:gsub("%d+", function(n) return string.format("%010d", n) end)
      end
      return pad_numbers(x.name) < pad_numbers(y.name)
    end
  end)
  return nodes
end

local function flatten_tree(node, depth, result)
  depth = depth or 0
  result = result or {}

  if not node or not node.children then return result end

  local sorted_items = sort_nodes(node.children)
  for _, item in ipairs(sorted_items) do
    table.insert(result, { item = item, depth = depth })
    if item.children and #item.children > 0 then flatten_tree(item, depth + 1, result) end
  end

  return result
end

---@return string|nil, string|nil
local function icon_and_hl(item)
  local icon, hl = config.icon_provider(item.type, item.path)
  if config.values.integrations.icon == "none" then return icon, hl end

  if item.type == "directory" then
    local icons = config.values.views.finder.icon
    local is_empty = item.open and item.children and #item.children == 0
    local is_expanded = item.open or false
    icon = is_empty and icons.directory_empty
      or (is_expanded and icons.directory_expanded or icons.directory_collapsed)
      or icon
  end

  return icon, hl
end

local function create_column_context(tag, node, flattened_entries, files_column)
  return {
    tag = tag,
    root_dir = node.path,
    entries = flattened_entries,

    get_entry_data = function(index)
      local entry = flattened_entries[index]
      if not entry then return nil end

      return {
        path = entry.item.path,
        name = entry.item.name,
        type = entry.item.type,
        depth = entry.depth,
        ref_id = entry.item.ref_id,
        item = entry.item,
      }
    end,

    get_all_paths = function()
      return util.tbl_map(flattened_entries, function(entry) return entry.item.path end)
    end,

    get_files_column = function() return files_column end,
  }
end

local M = {}

M.tag = 0

-- NOTE: Detail columns now return data via callback instead of directly updating UI
local columns = {
  link = function(ctx, _, _next)
    local column = {}

    for i = 1, #ctx.entries do
      local entry_data = ctx.get_entry_data(i)
      if entry_data and entry_data.item.link then
        table.insert(column, Text(nil, { virt_text = { { " --> " .. entry_data.path, "FylerFSLink" } } }))
      else
        table.insert(column, Text(nil, { virt_text = { { "" } } }))
      end
    end

    _next({ column = column, highlights = {} })
  end,

  git = function(ctx, _, _next)
    git.map_entries_async(ctx.root_dir, ctx.get_all_paths(), function(entries)
      local highlights, column = {}, {}

      for i, get_entry in ipairs(entries) do
        local entry_data = ctx.get_entry_data(i)
        if entry_data then
          local hl = get_entry[2]
          highlights[i] = hl or ((entry_data.type == "directory") and "FylerFSDirectoryName" or nil)
        end
        table.insert(column, Text(nil, { virt_text = { get_entry } }))
      end

      _next({ column = column, highlights = highlights })
    end)
  end,

  diagnostic = function(ctx, _, _next)
    diagnostic.map_entries_async(ctx.root_dir, ctx.get_all_paths(), function(entries)
      local highlights, column = {}, {}

      for i, get_entry in ipairs(entries) do
        local entry_data = ctx.get_entry_data(i)
        if entry_data then
          local hl = get_entry[2]
          highlights[i] = hl or ((entry_data.type == "directory") and "FylerFSDirectoryName" or nil)
        end
        table.insert(column, Text(nil, { virt_text = { get_entry } }))
      end

      _next({ column = column, highlights = highlights })
    end)
  end,

  permission = function(ctx, _, _next)
    local function get_permissions(path)
      local stat = Path.new(path):lstats()
      if not stat then return "----------" end

      local mode = stat.mode
      local perms = {}

      if stat.type == "directory" then
        table.insert(perms, "d")
      elseif stat.type == "link" then
        table.insert(perms, "l")
      else
        table.insert(perms, "-")
      end

      table.insert(perms, (mode % 512 >= 256) and "r" or "-")
      table.insert(perms, (mode % 256 >= 128) and "w" or "-")
      table.insert(perms, (mode % 128 >= 64) and "x" or "-")

      table.insert(perms, (mode % 64 >= 32) and "r" or "-")
      table.insert(perms, (mode % 32 >= 16) and "w" or "-")
      table.insert(perms, (mode % 16 >= 8) and "x" or "-")

      table.insert(perms, (mode % 8 >= 4) and "r" or "-")
      table.insert(perms, (mode % 4 >= 2) and "w" or "-")
      table.insert(perms, (mode % 2 >= 1) and "x" or "-")

      return table.concat(perms)
    end

    local highlights, column = {}, {}

    for i = 1, #ctx.entries do
      local entry_data = ctx.get_entry_data(i)
      if entry_data then
        local perms = get_permissions(entry_data.item.link or entry_data.path)
        table.insert(column, Text(nil, { virt_text = { { perms, "Comment" } } }))
      else
        table.insert(column, Text(nil, { virt_text = { { "" } } }))
      end
    end

    _next({ column = column, highlights = highlights })
  end,

  size = function(ctx, _, _next)
    local function get_size(path)
      if Path.new(path):is_directory() then return nil end

      local stat = Path.new(path):stats()
      if not stat then return nil end

      return stat.size
    end

    local function format_size(bytes)
      if not bytes or bytes < 0 then return "     -" end

      local units = { "B", "K", "M", "G", "T" }
      local unit_index = 1
      local size = bytes

      while size >= 1024 and unit_index < #units do
        size = size / 1024
        unit_index = unit_index + 1
      end

      local formatted
      if unit_index == 1 then
        formatted = string.format("%d%s", size, units[unit_index])
      else
        formatted = string.format("%.1f%s", size, units[unit_index])
      end

      return string.format("%6s", formatted)
    end

    local highlights, column = {}, {}

    for i = 1, #ctx.entries do
      table.insert(
        column,
        Text(nil, { virt_text = { { format_size(get_size(ctx.get_entry_data(i).path)), "Comment" } } })
      )
    end

    _next({ column = column, highlights = highlights })
  end,
}

local function collect_and_render_details(tag, context, files_column, oncollect)
  local results, enabled_columns = {}, {}
  local total, completed = 0, 0
  for _, column_name in ipairs(COLUMN_ORDER) do
    local cfg = config.values.views.finder.columns[column_name]
    if cfg and cfg.enabled and columns[column_name] then
      total = total + 1
      enabled_columns[column_name] = cfg
    end
  end

  if total == 0 then return end

  local function on_column_complete(column_name, column_data)
    if M.tag ~= tag then return end

    results[column_name] = column_data
    completed = completed + 1

    if completed == total then
      local all_highlights = {}
      for _, col_name in ipairs(COLUMN_ORDER) do
        local result = results[col_name]
        if result and result.highlights then
          for index, highlight in pairs(result.highlights) do
            all_highlights[index] = highlight
          end
        end
      end

      for index, highlight in pairs(all_highlights) do
        local row = files_column[index]
        if row and row.children then
          local name_component = row.children[4]
          if name_component then
            name_component.option = name_component.option or {}
            name_component.option.highlight = highlight
          end
        end
      end

      local detail_columns = { Column(files_column) }
      for _, col_name in ipairs(COLUMN_ORDER) do
        local result = results[col_name]
        if result and result.column then
          if #detail_columns > 0 then table.insert(detail_columns, Text("  ")) end

          table.insert(detail_columns, Column(result.column))
        end
      end

      oncollect({ tag = "files", children = { Row(detail_columns) } }, { partial = true })
    end
  end

  for column_name, cfg in pairs(enabled_columns) do
    local column_fn = columns[column_name]
    if column_fn then
      local success = pcall(function()
        column_fn(context, cfg, function(column_data) on_column_complete(column_name, column_data) end)
      end)

      if not success then on_column_complete(column_name, nil) end
    end
  end
end

M.files = Component.new_async(function(node, onupdate)
  M.tag = M.tag + 1

  local current_tag = M.tag
  if not node or not node.children then return onupdate({ tag = "files", children = {} }) end

  local flattened_entries = flatten_tree(node)
  if #flattened_entries == 0 then return onupdate({ tag = "files", children = {} }) end

  local files_column = {}
  for _, entry in ipairs(flattened_entries) do
    local item, depth = entry.item, entry.depth
    local icon, hl = icon_and_hl(item)
    local icon_highlight = (item.type == "directory") and "FylerFSDirectoryIcon" or hl
    local name_highlight = (item.type == "directory") and "FylerFSDirectoryName" or nil
    icon = icon and (icon .. "  ") or ""

    local indentation_text = Text(string.rep(" ", 2 * depth))
    local icon_text = Text(icon, { highlight = icon_highlight })
    local ref_id_text = item.ref_id and Text(string.format("/%05d ", item.ref_id)) or Text("")
    local name_text = Text(item.name, { highlight = name_highlight })
    table.insert(files_column, Row({ indentation_text, icon_text, ref_id_text, name_text }))
  end

  onupdate({ tag = "files", children = { Row({ Column(files_column) }) } })

  collect_and_render_details(
    current_tag,
    create_column_context(current_tag, node, flattened_entries, files_column),
    files_column,
    onupdate
  )
end)

M.operations = Component.new(function(operations)
  local types, details = {}, {}
  for _, operation in ipairs(operations) do
    if operation.type == "create" then
      table.insert(types, Text("CREATE", { highlight = "FylerGreen" }))
      table.insert(details, Text(operation.path))
    elseif operation.type == "delete" then
      table.insert(
        types,
        Text(config.values.views.finder.delete_to_trash and "TRASH" or "DELETE", { highlight = "FylerRed" })
      )
      table.insert(details, Text(operation.path))
    elseif operation.type == "move" then
      table.insert(types, Text("MOVE", { highlight = "FylerYellow" }))
      table.insert(details, Row({ Text(operation.src), Text(" > "), Text(operation.dst) }))
    elseif operation.type == "copy" then
      table.insert(types, Text("COPY", { highlight = "FylerBlue" }))
      table.insert(details, Row({ Text(operation.src), Text(" > "), Text(operation.dst) }))
    else
      error(string.format("Unknown operation type '%s'", operation.type))
    end
  end
  return {
    tag = "operations",
    children = {
      Text(""),
      Row({ Text("  "), Column(types), Text(" "), Column(details), Text("  ") }),
      Text(""),
    },
  }
end)

return M


================================================
FILE: lua/fyler/views/finder/watcher.lua
================================================
local Path = require("fyler.lib.path")
local config = require("fyler.config")
local util = require("fyler.lib.util")

---@class Watcher
---@field paths table<string, { fsevent: uv.uv_fs_event_t, running: boolean }>
---@field finder Finder
local Watcher = {}
Watcher.__index = Watcher

local instance = {}

---@return Watcher
function Watcher.new(finder) return setmetatable({ finder = finder, paths = {} }, Watcher) end

---@param dir string
function Watcher:start(dir)
  if not dir then return end

  if not Path.new(dir):is_directory() then
    self.paths[dir] = nil
    return
  end

  if not config.values.views.finder.watcher.enabled then return self end

  if not self.paths[dir] then
    self.paths[dir] = {
      fsevent = assert(vim.uv.new_fs_event()),
      running = false,
    }
  end

  if self.paths[dir].running then return self end

  self.paths[dir].fsevent:start(dir, {}, function(err, filename)
    if err then return end

    if
      filename == nil
      or filename:match("index")
      or filename:match("ORIG_HEAD")
      or filename:match("FETCH_HEAD")
      or filename:match("COMMIT_EDITMSG")
      or vim.endswith(filename, ".lock")
    then
      return
    end

    util.debounce(
      string.format("watcher:%d_%d_%s", self.finder.win.winid, self.finder.win.bufnr, dir),
      200,
      function() self.finder:dispatch_refresh({ force_update = true }) end
    )
  end)

  self.paths[dir].running = true
end

function Watcher:enable()
  for dir in pairs(self.paths) do
    self:start(dir)
  end
end

function Watcher:stop(dir)
  if not dir then return end

  if not Path.new(dir):is_directory() then
    self.paths[dir] = nil
    return
  end

  if not config.values.views.finder.watcher.enabled then return self end

  if self.paths[dir].running then self.paths[dir].fsevent:stop() end

  self.paths[dir].running = false
end

---@param should_clean boolean|nil
function Watcher:disable(should_clean)
  for dir in pairs(self.paths) do
    self:stop(dir)
  end

  if should_clean then self.paths = {} end
end

function Watcher.register(finder)
  local uri = finder.uri
  if not instance[uri] then instance[uri] = Watcher.new(finder) end
  return instance[uri]
end

return Watcher


================================================
FILE: lua/fyler.lua
================================================
--- INTRODUCTION
---
--- Fyler.nvim is a Neovim file manager plugin based on buffer-based file editing.
---
--- Why choose Fyler.nvim over |oil.nvim|?
--- - Provides a tree view.
--- - Users can have a full overview of their project without going back and forth
---   between directories.
---
--- GETTING STARTED
---
--- 1. Fyler must be setup correctly before use.
---
--- USAGE
---
--- Fyler can be used through commands or the Lua API.
---
--- COMMANDS
---
--- :Fyler dir=... kind=...
---
--- Parameters:
--- dir    Path to the directory to open
--- kind   Display method, one of:
---        - `float`
---        - `replace`
---        - `split_above`
---        - `split_above_all`
---        - `split_below`
---        - `split_below_all`
---        - `split_left`
---        - `split_left_most`
---        - `split_right`
---        - `split_right_most`
---
--- LUA API
---
--- >lua
---     local fyler = require("fyler")
---
---     -- Opens finder view with given options
---     fyler.open({ dir = "...", kind = "..." })
---
---     -- Toggles finder view with given options
---     fyler.toggle({ dir = "...", kind = "..." })
---
---     -- Focuses finder view
---     fyler.focus()
---
---     -- Focuses given file path or alternate buffer
---     fyler.navigate("...")
--- <
---
---@tag fyler.nvim

local M = {}

local did_setup = false

---@param opts FylerSetup|nil
function M.setup(opts)
  if vim.fn.has("nvim-0.11") == 0 then return vim.notify("Fyler requires at least NVIM 0.11") end

  if did_setup then return end

  require("fyler.config").setup(opts)

  did_setup = true

  local finder = setmetatable({}, { __index = function(_, k) return require("fyler.views.finder")[k] end })

  -- Fyler.API: Opens finder view with provided options
  M.open = function(args)
    args = args or {}
    finder.open(args.dir, args.kind)
  end

  -- Fyler.API: Closes current finder view
  M.close = finder.close

  -- Fyler.API: Toggles finder view with provided options
  M.toggle = function(args)
    args = args or {}
    finder.toggle(args.dir, args.kind)
  end

  -- Fyler.API: Focus finder view
  M.focus = finder.focus

  -- Fyler.API: Focuses given file path
  M.navigate = function(path) finder.navigate(path) end
end

return M


================================================
FILE: lua/telescope/_extensions/fyler.lua
================================================
local has_telescope, telescope = pcall(require, "telescope")

if not has_telescope then error("Fyler.nvim requires telescope.nvim") end

local action_state = require("telescope.actions.state")
local actions = require("telescope.actions")
local config = require("telescope.config")
local finders = require("telescope.finders")
local fyler = require("fyler")
local pickers = require("telescope.pickers")
local util = require("fyler.lib.util")

local default_opts = {
  sorting_strategy = "ascending",
  layout_config = {
    horizontal = {
      prompt_position = "top",
    },
  },
}

local finder = finders.new_async_job({
  command_generator = function() return { "zoxide", "query", "--list" } end,
  entry_maker = function(entry)
    if not entry or entry == "" then return nil end

    local display_name = vim.fn.fnamemodify(entry, ":t")
    return {
      value = entry,
      display = display_name .. " (" .. entry .. ")",
      ordinal = entry,
    }
  end,
})

return telescope.register_extension({
  exports = {
    setup = function(opts) default_opts = util.tbl_merge_force(default_opts, opts) end,
    zoxide = function()
      pickers
        .new(default_opts, {
          debounce = 100,
          prompt_title = "Fyler Zoxide",
          finder = finder,
          sorter = config.values.generic_sorter(),
          attach_mappings = function(prompt_bufnr)
            actions.select_default:replace(function()
              local selection = action_state.get_selected_entry()
              actions.close(prompt_bufnr)

              if selection then fyler.open({ dir = selection.value }) end
            end)

            return true
          end,
        })
        :find()
    end,
  },
})


================================================
FILE: plugin/fyler.lua
================================================
vim.api.nvim_create_user_command("Fyler", function(args)
  local util = require("fyler.lib.util")
  local opts = {}
  for _, farg in ipairs(args.fargs) do
    local k, v = util.unpack(vim.split(farg, "="))
    opts[k] = v
  end

  if opts.dir == nil then
    ---@type string[]|nil
    local range_lines = nil

    -- Check if the com
Download .txt
gitextract_2i4brai5/

├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug-report.yml
│   │   ├── config.yml
│   │   └── feature-request.yml
│   ├── pull_request_template.md
│   └── workflows/
│       └── ci.yaml
├── .gitignore
├── .luarc.json
├── .markdownlint.json
├── .stylua.toml
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── bin/
│   ├── gen_vimdoc.lua
│   └── run_tests.lua
├── doc/
│   └── fyler.txt
├── lua/
│   ├── fyler/
│   │   ├── autocmds.lua
│   │   ├── config.lua
│   │   ├── deprecated.lua
│   │   ├── hooks.lua
│   │   ├── input.lua
│   │   ├── inputs/
│   │   │   ├── confirm.lua
│   │   │   └── winpick.lua
│   │   ├── integrations/
│   │   │   ├── icon/
│   │   │   │   ├── init.lua
│   │   │   │   ├── mini_icons.lua
│   │   │   │   ├── nvim_web_devicons.lua
│   │   │   │   └── vim_nerdfont.lua
│   │   │   └── winpick/
│   │   │       ├── init.lua
│   │   │       ├── nvim_window_picker.lua
│   │   │       └── snacks.lua
│   │   ├── lib/
│   │   │   ├── async.lua
│   │   │   ├── diagnostic.lua
│   │   │   ├── fs.lua
│   │   │   ├── git.lua
│   │   │   ├── hl.lua
│   │   │   ├── path.lua
│   │   │   ├── process.lua
│   │   │   ├── spinner.lua
│   │   │   ├── structs/
│   │   │   │   ├── list.lua
│   │   │   │   ├── stack.lua
│   │   │   │   └── trie.lua
│   │   │   ├── trash/
│   │   │   │   ├── init.lua
│   │   │   │   ├── linux.lua
│   │   │   │   ├── macos.lua
│   │   │   │   └── windows.lua
│   │   │   ├── ui/
│   │   │   │   ├── component.lua
│   │   │   │   ├── init.lua
│   │   │   │   └── renderer.lua
│   │   │   ├── util.lua
│   │   │   └── win.lua
│   │   ├── log.lua
│   │   └── views/
│   │       └── finder/
│   │           ├── actions.lua
│   │           ├── files/
│   │           │   ├── init.lua
│   │           │   ├── manager.lua
│   │           │   └── resolver.lua
│   │           ├── helper.lua
│   │           ├── indent.lua
│   │           ├── init.lua
│   │           ├── ui.lua
│   │           └── watcher.lua
│   ├── fyler.lua
│   └── telescope/
│       └── _extensions/
│           └── fyler.lua
├── plugin/
│   └── fyler.lua
├── selene/
│   ├── config.toml
│   └── globals.toml
├── syntax/
│   └── fyler.lua
└── tests/
    ├── README.md
    ├── helper.lua
    ├── integration/
    │   ├── test_finder_mappings.lua
    │   └── test_finder_mutation.lua
    ├── minimal_init.lua
    ├── screenshots/
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'float'-}
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'float'-}-002
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'float'-}-003
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'float'-}-004
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'float'-}-005
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'float'-}-006
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'float'-}-007
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'float'-}-008
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'float'-}-009
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'replace'-}
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'replace'-}-002
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'replace'-}-003
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'replace'-}-004
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'replace'-}-005
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'replace'-}-006
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'replace'-}-007
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'replace'-}-008
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'replace'-}-009
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above'-}
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above'-}-002
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above'-}-003
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above'-}-004
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above'-}-005
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above'-}-006
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above'-}-007
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above'-}-008
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above'-}-009
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above_all'-}
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above_all'-}-002
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above_all'-}-003
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above_all'-}-004
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above_all'-}-005
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above_all'-}-006
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above_all'-}-007
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above_all'-}-008
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above_all'-}-009
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below'-}
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below'-}-002
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below'-}-003
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below'-}-004
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below'-}-005
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below'-}-006
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below'-}-007
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below'-}-008
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below'-}-009
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below_all'-}
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below_all'-}-002
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below_all'-}-003
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below_all'-}-004
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below_all'-}-005
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below_all'-}-006
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below_all'-}-007
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below_all'-}-008
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below_all'-}-009
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left'-}
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left'-}-002
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left'-}-003
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left'-}-004
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left'-}-005
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left'-}-006
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left'-}-007
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left'-}-008
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left'-}-009
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left_most'-}
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left_most'-}-002
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left_most'-}-003
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left_most'-}-004
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left_most'-}-005
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left_most'-}-006
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left_most'-}-007
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left_most'-}-008
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left_most'-}-009
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right'-}
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right'-}-002
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right'-}-003
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right'-}-004
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right'-}-005
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right'-}-006
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right'-}-007
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right'-}-008
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right'-}-009
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right_most'-}
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right_most'-}-002
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right_most'-}-003
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right_most'-}-004
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right_most'-}-005
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right_most'-}-006
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right_most'-}-007
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right_most'-}-008
    │   ├── tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right_most'-}-009
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Navigate-+-args-{-'float'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Navigate-+-args-{-'replace'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Navigate-+-args-{-'split_above'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Navigate-+-args-{-'split_above_all'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Navigate-+-args-{-'split_below'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Navigate-+-args-{-'split_below_all'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Navigate-+-args-{-'split_left'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Navigate-+-args-{-'split_left_most'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Navigate-+-args-{-'split_right'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Navigate-+-args-{-'split_right_most'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-With-Arguments-+-args-{-'float'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-With-Arguments-+-args-{-'replace'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-With-Arguments-+-args-{-'split_above'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-With-Arguments-+-args-{-'split_above_all'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-With-Arguments-+-args-{-'split_below'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-With-Arguments-+-args-{-'split_below_all'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-With-Arguments-+-args-{-'split_left'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-With-Arguments-+-args-{-'split_left_most'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-With-Arguments-+-args-{-'split_right'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-With-Arguments-+-args-{-'split_right_most'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-Without-Arguments-+-args-{-'float'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-Without-Arguments-+-args-{-'replace'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-Without-Arguments-+-args-{-'split_above'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-Without-Arguments-+-args-{-'split_above_all'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-Without-Arguments-+-args-{-'split_below'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-Without-Arguments-+-args-{-'split_below_all'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-Without-Arguments-+-args-{-'split_left'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-Without-Arguments-+-args-{-'split_left_most'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-Without-Arguments-+-args-{-'split_right'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Open-Without-Arguments-+-args-{-'split_right_most'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'float'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'float'-}-002
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'replace'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'replace'-}-002
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_above'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_above'-}-002
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_above_all'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_above_all'-}-002
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_below'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_below'-}-002
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_below_all'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_below_all'-}-002
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_left'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_left'-}-002
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_left_most'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_left_most'-}-002
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_right'-}
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_right'-}-002
    │   ├── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_right_most'-}
    │   └── tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_right_most'-}-002
    └── unit/
        ├── test_api.lua
        ├── test_mini_icon.lua
        └── test_setup.lua
Condensed preview — 214 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (759K chars).
[
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.yml",
    "chars": 1327,
    "preview": "name: Bug report\ndescription: Report a problem to help us improve\ntitle: \"[BUG] \"\nlabels: [bug]\nbody:\n  - type: checkbox"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 204,
    "preview": "blank_issues_enabled: false\ncontact_links:\n  - name: Question\n    url: https://github.com/A7Lavinraj/fyler.nvim/discussi"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature-request.yml",
    "chars": 307,
    "preview": "name: Feature request\ndescription: Describe a feature you would like to see\ntitle: \"[FEAT] \"\nlabels: [feature request]\nb"
  },
  {
    "path": ".github/pull_request_template.md",
    "chars": 115,
    "preview": "- [ ] I have read and follow [CONTRIBUTING.md](https://github.com/A7Lavinraj/fyler.nvim/blob/main/CONTRIBUTING.md)\n"
  },
  {
    "path": ".github/workflows/ci.yaml",
    "chars": 771,
    "preview": "name: CI\non:\n  - push\n  - pull_request\njobs:\n  format:\n    name: Formatting\n    runs-on: ubuntu-latest\n    steps:\n      "
  },
  {
    "path": ".gitignore",
    "chars": 179,
    "preview": "# Temporary test directory\n.temp\n\n# Quick testing directory for local development\n.scratch\n\n# Take notes for any potenti"
  },
  {
    "path": ".luarc.json",
    "chars": 260,
    "preview": "{\n    \"$schema\": \"https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json\",\n    \"diagnostics.disab"
  },
  {
    "path": ".markdownlint.json",
    "chars": 57,
    "preview": "{\n  \"MD033\": false,\n  \"MD041\": false,\n  \"MD013\": false\n}\n"
  },
  {
    "path": ".stylua.toml",
    "chars": 201,
    "preview": "collapse_simple_statement = \"Always\"\ncolumn_width = 120\nindent_type = \"Spaces\"\nindent_width = 2\nline_endings = \"Unix\"\nqu"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 717,
    "preview": "# Contributing Guide\n\nWe welcome contributions! Here's how to get started:\n\n## Getting Started\n\n- Fork the repository\n- "
  },
  {
    "path": "LICENSE",
    "chars": 11349,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "Makefile",
    "chars": 1403,
    "preview": ".SILENT:\n\n.PHONY: init\ninit: format lint test doc\n\n.PHONY: deps\ndeps:\n\t@if [ ! -d .temp/deps/mini.test ]; then \\\n\t\techo "
  },
  {
    "path": "README.md",
    "chars": 3150,
    "preview": "<div align=\"center\">\n  <h1>Fyler.nvim</h1>\n  <table>\n    <tr>\n      <td>\n        <strong>A file manager for <a href=\"htt"
  },
  {
    "path": "bin/gen_vimdoc.lua",
    "chars": 672,
    "preview": "vim.opt.runtimepath:prepend(vim.fn.getcwd())\nvim.opt.runtimepath:prepend(vim.fs.joinpath(vim.fn.getcwd(), \".temp\", \"deps"
  },
  {
    "path": "bin/run_tests.lua",
    "chars": 451,
    "preview": "require(\"mini.test\").run({\n  execute = { stop_on_error = true },\n  collect = {\n    find_files = function() return vim.fn"
  },
  {
    "path": "doc/fyler.txt",
    "chars": 8013,
    "preview": "                                                                    *fyler.nvim*\nINTRODUCTION\n\nFyler.nvim is a Neovim fi"
  },
  {
    "path": "lua/fyler/autocmds.lua",
    "chars": 2533,
    "preview": "local M = {}\n\nlocal augroup = vim.api.nvim_create_augroup(\"fyler_augroup_global\", { clear = true })\n\nfunction M.setup(co"
  },
  {
    "path": "lua/fyler/config.lua",
    "chars": 12381,
    "preview": "local deprecated = require(\"fyler.deprecated\")\nlocal util = require(\"fyler.lib.util\")\n\nlocal config = {}\n\nlocal DEPRECAT"
  },
  {
    "path": "lua/fyler/deprecated.lua",
    "chars": 4105,
    "preview": "local M = {}\n\nlocal warnings = {}\n\nlocal function split_path(path)\n  local parts = {}\n  for part in path:gmatch(\"[^.]+\")"
  },
  {
    "path": "lua/fyler/hooks.lua",
    "chars": 2930,
    "preview": "local M = {}\nlocal hooks = {}\n\n-- Get attached active LSP clients\nlocal function get_active_lsp_clients()\n  if vim.lsp.g"
  },
  {
    "path": "lua/fyler/input.lua",
    "chars": 215,
    "preview": "local M = setmetatable({}, {\n  __index = function(_, key)\n    local ok, input = pcall(require, \"fyler.inputs.\" .. key)\n "
  },
  {
    "path": "lua/fyler/inputs/confirm.lua",
    "chars": 2335,
    "preview": "local Ui = require(\"fyler.lib.ui\")\nlocal Win = require(\"fyler.lib.win\")\nlocal util = require(\"fyler.lib.util\")\n\nlocal Co"
  },
  {
    "path": "lua/fyler/inputs/winpick.lua",
    "chars": 1543,
    "preview": "local Ui = require(\"fyler.lib.ui\")\nlocal Win = require(\"fyler.lib.win\")\nlocal util = require(\"fyler.lib.util\")\n\nlocal M "
  },
  {
    "path": "lua/fyler/integrations/icon/init.lua",
    "chars": 449,
    "preview": "---@class IconIntegration\n---@field mini_icon MiniIconsIntegration\n---@field nvim_web_devicons NvimWebDeviconsIntegratio"
  },
  {
    "path": "lua/fyler/integrations/icon/mini_icons.lua",
    "chars": 452,
    "preview": "---@class MiniIconsIntegration\nlocal M = {}\n\nfunction M.get(type, name)\n  local ok, miniicons = pcall(require, \"mini.ico"
  },
  {
    "path": "lua/fyler/integrations/icon/nvim_web_devicons.lua",
    "chars": 419,
    "preview": "---@class NvimWebDeviconsIntegration\nlocal M = {}\n\nfunction M.get(type, path)\n  local ok, devicons = pcall(require, \"nvi"
  },
  {
    "path": "lua/fyler/integrations/icon/vim_nerdfont.lua",
    "chars": 310,
    "preview": "---@class VimNerdfontIntegration\nlocal M = {}\n\nfunction M.get(type, path)\n  assert(vim.fn.exists(\"*nerdfont#find\"), \"vim"
  },
  {
    "path": "lua/fyler/integrations/winpick/init.lua",
    "chars": 1199,
    "preview": "---@class WinpickIntegration\n---@field none fun(win_filter: integer[], onsubmit: fun(winid: integer|nil), opts: table|ni"
  },
  {
    "path": "lua/fyler/integrations/winpick/nvim_window_picker.lua",
    "chars": 1077,
    "preview": "---@class NvimWindowPickerIntegration\nlocal M = {}\n\n--- Note: win_filter is unused here because we filter by filetype in"
  },
  {
    "path": "lua/fyler/integrations/winpick/snacks.lua",
    "chars": 963,
    "preview": "---@class SnacksWinpickIntegration\nlocal M = {}\n\n--- Note: win_filter is unused here because snacks.picker.util.pick_win"
  },
  {
    "path": "lua/fyler/lib/async.lua",
    "chars": 2626,
    "preview": "local log = require(\"fyler.log\")\nlocal util = require(\"fyler.lib.util\")\n\nlocal M = {}\n\nlocal function trace_error(messag"
  },
  {
    "path": "lua/fyler/lib/diagnostic.lua",
    "chars": 2337,
    "preview": "local config = require(\"fyler.config\")\nlocal util = require(\"fyler.lib.util\")\n\nlocal M = {}\n\nlocal severity_names = {\n  "
  },
  {
    "path": "lua/fyler/lib/fs.lua",
    "chars": 9070,
    "preview": "local Path = require(\"fyler.lib.path\")\nlocal hooks = require(\"fyler.hooks\")\nlocal util = require(\"fyler.lib.util\")\n\nloca"
  },
  {
    "path": "lua/fyler/lib/git.lua",
    "chars": 2689,
    "preview": "local Path = require(\"fyler.lib.path\")\nlocal Process = require(\"fyler.lib.process\")\nlocal config = require(\"fyler.config"
  },
  {
    "path": "lua/fyler/lib/hl.lua",
    "chars": 3448,
    "preview": "local M = {}\n\n---@param dec integer\nlocal function to_hex(dec) return string.format(\"%06X\", math.max(0, math.min(0xFFFFF"
  },
  {
    "path": "lua/fyler/lib/path.lua",
    "chars": 4874,
    "preview": "local util = require(\"fyler.lib.util\")\n\n---@class Path\n---@field _original string\n---@field _segments string[]|nil\nlocal"
  },
  {
    "path": "lua/fyler/lib/process.lua",
    "chars": 2564,
    "preview": "---@class ProcessOpts\n---@field path string\n---@field args string[]|nil\n---@field stdin string|nil\n\n---@class Process\n--"
  },
  {
    "path": "lua/fyler/lib/spinner.lua",
    "chars": 1510,
    "preview": "local util = require(\"fyler.lib.util\")\n\n---@class Spinner\n---@field text string\n---@field count number\n---@field interva"
  },
  {
    "path": "lua/fyler/lib/structs/list.lua",
    "chars": 1833,
    "preview": "---@class LinkedList\n---@field node LinkedListNode\nlocal LinkedList = {}\nLinkedList.__index = LinkedList\n\n---@class Link"
  },
  {
    "path": "lua/fyler/lib/structs/stack.lua",
    "chars": 628,
    "preview": "---@class Stack\n---@field items table\nlocal Stack = {}\nStack.__index = Stack\n\n---@return Stack\nfunction Stack.new() retu"
  },
  {
    "path": "lua/fyler/lib/structs/trie.lua",
    "chars": 1877,
    "preview": "---@class Trie\n---@field value any\n---@field children table<string, Trie>\nlocal Trie = {}\nTrie.__index = Trie\n\n---@param"
  },
  {
    "path": "lua/fyler/lib/trash/init.lua",
    "chars": 355,
    "preview": "local M = setmetatable({}, {\n  __index = function(_, key)\n    local Path = require(\"fyler.lib.path\")\n    if Path.is_wind"
  },
  {
    "path": "lua/fyler/lib/trash/linux.lua",
    "chars": 2038,
    "preview": "local Path = require(\"fyler.lib.path\")\nlocal fs = require(\"fyler.lib.fs\")\n\nlocal M = {}\n\n---@param opts {dir: string, ba"
  },
  {
    "path": "lua/fyler/lib/trash/macos.lua",
    "chars": 564,
    "preview": "local Path = require(\"fyler.lib.path\")\n\nlocal M = {}\n\nfunction M.dump(opts, _next)\n  local abspath = Path.new(opts.path)"
  },
  {
    "path": "lua/fyler/lib/trash/windows.lua",
    "chars": 1582,
    "preview": "local Path = require(\"fyler.lib.path\")\nlocal M = {}\n\nfunction M.dump(opts, _next)\n  local abspath = Path.new(opts.path):"
  },
  {
    "path": "lua/fyler/lib/ui/component.lua",
    "chars": 2107,
    "preview": "local util = require(\"fyler.lib.util\")\n\n---@class UiComponentOption\n---@field highlight string|nil\n---@field virt_text s"
  },
  {
    "path": "lua/fyler/lib/ui/init.lua",
    "chars": 2011,
    "preview": "local Component = require(\"fyler.lib.ui.component\")\nlocal Renderer = require(\"fyler.lib.ui.renderer\")\n\n---@class Ui\n---@"
  },
  {
    "path": "lua/fyler/lib/ui/renderer.lua",
    "chars": 9118,
    "preview": "---@class UiRenderer\n---@field line string[]\n---@field extmark table[]\n---@field highlight table[]\n---@field flag table\n"
  },
  {
    "path": "lua/fyler/lib/util.lua",
    "chars": 4649,
    "preview": "local M = {}\n\n---@param n integer\n---@param ... any\nfunction M.select_n(n, ...)\n  local x = select(n, ...)\n  return x\nen"
  },
  {
    "path": "lua/fyler/lib/win.lua",
    "chars": 10900,
    "preview": "local util = require(\"fyler.lib.util\")\n\n---@alias WinKind\n---| \"float\"\n---| \"replace\"\n---| \"split_above\"\n---| \"split_abo"
  },
  {
    "path": "lua/fyler/log.lua",
    "chars": 467,
    "preview": "local M = {}\n\n---@param message string\n---@param level integer\nlocal __log = vim.schedule_wrap(function(message, level) "
  },
  {
    "path": "lua/fyler/views/finder/actions.lua",
    "chars": 5324,
    "preview": "local Path = require(\"fyler.lib.path\")\nlocal config = require(\"fyler.config\")\nlocal helper = require(\"fyler.views.finder"
  },
  {
    "path": "lua/fyler/views/finder/files/init.lua",
    "chars": 8256,
    "preview": "local Path = require(\"fyler.lib.path\")\nlocal Trie = require(\"fyler.lib.structs.trie\")\nlocal fs = require(\"fyler.lib.fs\")"
  },
  {
    "path": "lua/fyler/views/finder/files/manager.lua",
    "chars": 2396,
    "preview": "local util = require(\"fyler.lib.util\")\n\n---@class FilesEntry\n---@field ref_id integer\n---@field open boolean\n---@field u"
  },
  {
    "path": "lua/fyler/views/finder/files/resolver.lua",
    "chars": 12233,
    "preview": "local Path = require(\"fyler.lib.path\")\nlocal helper = require(\"fyler.views.finder.helper\")\nlocal manager = require(\"fyle"
  },
  {
    "path": "lua/fyler/views/finder/helper.lua",
    "chars": 1499,
    "preview": "local M = {}\n\n---@param uri string|nil\n---@return boolean\nfunction M.is_protocol_uri(uri) return uri and (not not uri:ma"
  },
  {
    "path": "lua/fyler/views/finder/indent.lua",
    "chars": 4526,
    "preview": "local config = require(\"fyler.config\")\n\nlocal M = {}\n\nlocal INDENT_WIDTH = 2\nlocal snapshots = {}\n\n---@param winid integ"
  },
  {
    "path": "lua/fyler/views/finder/init.lua",
    "chars": 11864,
    "preview": "local Path = require(\"fyler.lib.path\")\nlocal async = require(\"fyler.lib.async\")\nlocal config = require(\"fyler.config\")\nl"
  },
  {
    "path": "lua/fyler/views/finder/ui.lua",
    "chars": 11646,
    "preview": "local Path = require(\"fyler.lib.path\")\nlocal Ui = require(\"fyler.lib.ui\")\nlocal config = require(\"fyler.config\")\nlocal d"
  },
  {
    "path": "lua/fyler/views/finder/watcher.lua",
    "chars": 2212,
    "preview": "local Path = require(\"fyler.lib.path\")\nlocal config = require(\"fyler.config\")\nlocal util = require(\"fyler.lib.util\")\n\n--"
  },
  {
    "path": "lua/fyler.lua",
    "chars": 2244,
    "preview": "--- INTRODUCTION\n---\n--- Fyler.nvim is a Neovim file manager plugin based on buffer-based file editing.\n---\n--- Why choo"
  },
  {
    "path": "lua/telescope/_extensions/fyler.lua",
    "chars": 1711,
    "preview": "local has_telescope, telescope = pcall(require, \"telescope\")\n\nif not has_telescope then error(\"Fyler.nvim requires teles"
  },
  {
    "path": "plugin/fyler.lua",
    "chars": 2716,
    "preview": "vim.api.nvim_create_user_command(\"Fyler\", function(args)\n  local util = require(\"fyler.lib.util\")\n  local opts = {}\n  fo"
  },
  {
    "path": "selene/config.toml",
    "chars": 82,
    "preview": "std = \"selene/globals\"\n\n[rules]\nshadowing = \"allow\"\nmultiple_statements = \"allow\"\n"
  },
  {
    "path": "selene/globals.toml",
    "chars": 77,
    "preview": "[selene]\nbase = \"lua51\"\nname = \"globals\"\n\n[vim]\nany = true\n\n[bit]\nany = true\n"
  },
  {
    "path": "syntax/fyler.lua",
    "chars": 147,
    "preview": "vim.cmd([[\n  if exists(\"b:current_syntax\")\n    finish\n  endif\n\n  syn match FylerReferenceId /\\/\\d* / conceal\n\n  let b:cu"
  },
  {
    "path": "tests/README.md",
    "chars": 396,
    "preview": "# Fyler Tests\n\n### Running Tests\n\nAs a base requirement you must have `make` installed.\n\nOnce `make` is installed you ca"
  },
  {
    "path": "tests/helper.lua",
    "chars": 2505,
    "preview": "local MiniTest = require(\"mini.test\")\n\nlocal M = {}\n\nM.equal = MiniTest.expect.equality\nM.match_pattern = MiniTest.new_e"
  },
  {
    "path": "tests/integration/test_finder_mappings.lua",
    "chars": 2302,
    "preview": "local helper = require(\"tests.helper\")\n\nlocal nvim = helper.new_neovim()\n\nlocal function make_tree(children)\n  local tem"
  },
  {
    "path": "tests/integration/test_finder_mutation.lua",
    "chars": 4101,
    "preview": "local helper = require(\"tests.helper\")\n\nlocal nvim = helper.new_neovim()\nlocal equal = helper.equal\n\nlocal function make"
  },
  {
    "path": "tests/minimal_init.lua",
    "chars": 1235,
    "preview": "-- Immediately add plugins to runtimepath\nvim.opt.runtimepath:prepend(vim.fn.getcwd())\nvim.opt.runtimepath:prepend(vim.f"
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'float'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'float'-}-002",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'float'-}-003",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'float'-}-004",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'float'-}-005",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'float'-}-006",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'float'-}-007",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|---FILE CONTENT---               "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'float'-}-008",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|---FILE CONTENT---               "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'float'-}-009",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'replace'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  aab-dir                       "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'replace'-}-002",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  aab-dir                       "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'replace'-}-003",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  aa-dir                        "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'replace'-}-004",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  a-dir                         "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'replace'-}-005",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  a-dir                         "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'replace'-}-006",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'replace'-}-007",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|---FILE CONTENT---               "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'replace'-}-008",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|---FILE CONTENT---               "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'replace'-}-009",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  aab-dir                       "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above'-}-002",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  aab-dir                       "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above'-}-003",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  aa-dir                        "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above'-}-004",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  a-dir                         "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above'-}-005",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  a-dir                         "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above'-}-006",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above'-}-007",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|---FILE CONTENT---               "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above'-}-008",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|---FILE CONTENT---               "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above'-}-009",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above_all'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  aab-dir                       "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above_all'-}-002",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  aab-dir                       "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above_all'-}-003",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  aa-dir                        "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above_all'-}-004",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  a-dir                         "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above_all'-}-005",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  a-dir                         "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above_all'-}-006",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above_all'-}-007",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|---FILE CONTENT---               "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above_all'-}-008",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|---FILE CONTENT---               "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_above_all'-}-009",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below'-}-002",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below'-}-003",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below'-}-004",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below'-}-005",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below'-}-006",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below'-}-007",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|---FILE CONTENT---               "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below'-}-008",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|---FILE CONTENT---               "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below'-}-009",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below_all'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below_all'-}-002",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below_all'-}-003",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below_all'-}-004",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below_all'-}-005",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below_all'-}-006",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below_all'-}-007",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|---FILE CONTENT---               "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below_all'-}-008",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|---FILE CONTENT---               "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_below_all'-}-009",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  aab-dir              │        "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left'-}-002",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  aab-dir              │        "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left'-}-003",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  aa-dir               │        "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left'-}-004",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  a-dir                │        "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left'-}-005",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  a-dir                │        "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left'-}-006",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left'-}-007",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|---FILE CONTENT---               "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left'-}-008",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|---FILE CONTENT---               "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left'-}-009",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left_most'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  aab-dir              │        "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left_most'-}-002",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  aab-dir              │        "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left_most'-}-003",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  aa-dir               │        "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left_most'-}-004",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  a-dir                │        "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left_most'-}-005",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  a-dir                │        "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left_most'-}-006",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left_most'-}-007",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|---FILE CONTENT---               "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left_most'-}-008",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|---FILE CONTENT---               "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_left_most'-}-009",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right'-}-002",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right'-}-003",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right'-}-004",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right'-}-005",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right'-}-006",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right'-}-007",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|---FILE CONTENT---               "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right'-}-008",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|---FILE CONTENT---               "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right'-}-009",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right_most'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right_most'-}-002",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right_most'-}-003",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right_most'-}-004",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right_most'-}-005",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right_most'-}-006",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right_most'-}-007",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|---FILE CONTENT---               "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right_most'-}-008",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|---FILE CONTENT---               "
  },
  {
    "path": "tests/screenshots/tests-integration-test_finder_mappings.lua---Each-WinKind-Can---Handle-Default-Mappings-+-args-{-'split_right_most'-}-009",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Navigate-+-args-{-'float'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Navigate-+-args-{-'replace'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  a-dir                         "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Navigate-+-args-{-'split_above'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  a-dir                         "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Navigate-+-args-{-'split_above_all'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  a-dir                         "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Navigate-+-args-{-'split_below'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Navigate-+-args-{-'split_below_all'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Navigate-+-args-{-'split_left'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  a-dir                │        "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Navigate-+-args-{-'split_left_most'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  a-dir                │        "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Navigate-+-args-{-'split_right'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Navigate-+-args-{-'split_right_most'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Open-With-Arguments-+-args-{-'float'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Open-With-Arguments-+-args-{-'replace'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  a-dir                         "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Open-With-Arguments-+-args-{-'split_above'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  a-dir                         "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Open-With-Arguments-+-args-{-'split_above_all'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  a-dir                         "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Open-With-Arguments-+-args-{-'split_below'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Open-With-Arguments-+-args-{-'split_below_all'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Open-With-Arguments-+-args-{-'split_left'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  a-dir                │        "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Open-With-Arguments-+-args-{-'split_left_most'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  a-dir                │        "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Open-With-Arguments-+-args-{-'split_right'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Open-With-Arguments-+-args-{-'split_right_most'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Open-Without-Arguments-+-args-{-'float'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Open-Without-Arguments-+-args-{-'replace'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  a-dir                         "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Open-Without-Arguments-+-args-{-'split_above'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  a-dir                         "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Open-Without-Arguments-+-args-{-'split_above_all'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  a-dir                         "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Open-Without-Arguments-+-args-{-'split_below'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Open-Without-Arguments-+-args-{-'split_below_all'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Open-Without-Arguments-+-args-{-'split_left'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  a-dir                │        "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Open-Without-Arguments-+-args-{-'split_left_most'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  a-dir                │        "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Open-Without-Arguments-+-args-{-'split_right'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Open-Without-Arguments-+-args-{-'split_right_most'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'float'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'float'-}-002",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'replace'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  a-dir                         "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'replace'-}-002",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_above'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  a-dir                         "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_above'-}-002",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_above_all'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|󰉋  a-dir                         "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_above_all'-}-002",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  },
  {
    "path": "tests/screenshots/tests-unit-test_api.lua---Each-WinKind-Can---Toggle-With-Arguments-+-args-{-'split_below'-}",
    "chars": 3529,
    "preview": "--|---------|---------|---------|---------|---------|---------|---------|---------|\n01|                                 "
  }
]

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

About this extraction

This page contains the full source code of the A7Lavinraj/fyler.nvim GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 214 files (694.2 KB), approximately 173.6k tokens. 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!