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
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.