mirror of
https://github.com/harivansh-afk/oil.nvim.git
synced 2026-04-18 14:02:54 +00:00
Compare commits
109 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f55b25e493 | ||
|
|
7a09f0b000 | ||
|
|
6b59a6cf62 | ||
|
|
fbbb2a9872 | ||
|
|
d278dc40f9 | ||
|
|
43227c5a1c | ||
|
|
24055701b7 | ||
|
|
81b8a91735 | ||
|
|
78ed0cf7d9 | ||
|
|
963c8d2c55 | ||
|
|
634049414b | ||
|
|
bbfa7cba85 | ||
|
|
756dec855b | ||
|
|
09a4e4f460 | ||
|
|
3b249b7195 | ||
|
|
15a2b21eda | ||
|
|
cbcb3f997f | ||
|
|
b9ab05fe5a | ||
|
|
e5a1398790 | ||
|
|
e5bd931edb | ||
|
|
01cb3a8ad7 | ||
|
|
7e1cd7703f | ||
|
|
71948729cd | ||
|
|
f55ebb0079 | ||
|
|
64dbcaa91d | ||
|
|
dfb09e87bf | ||
|
|
200df01e4b | ||
|
|
919e155fdf | ||
|
|
07f80ad645 | ||
|
|
bbad9a76b2 | ||
|
|
3b7c74798e | ||
|
|
1498d2fccf | ||
|
|
08c2bce8b0 | ||
|
|
5b6068aad7 | ||
|
|
35f7f000f4 | ||
|
|
685cdb4ffa | ||
|
|
302bbaceea | ||
|
|
5b38bfe279 | ||
|
|
ba1f50a9a8 | ||
|
|
ab887d926c | ||
|
|
4c9bdf0d83 | ||
|
|
8649818fb2 | ||
|
|
548587d68b | ||
|
|
54fe7dca36 | ||
|
|
d7c61c7084 | ||
|
|
975a77cce3 | ||
|
|
7cde5aab10 | ||
|
|
32dd3e378d | ||
|
|
5313690956 | ||
|
|
8abc58b038 | ||
|
|
abbfbd0dbc | ||
|
|
20baf82747 | ||
|
|
add50252b5 | ||
|
|
b594b9a905 | ||
|
|
a3fc6623fa | ||
|
|
81b2c5f04a | ||
|
|
6f9e1057c5 | ||
|
|
2f6ed70161 | ||
|
|
57528bf9c5 | ||
|
|
52f1683c76 | ||
|
|
83ac5185f7 | ||
|
|
7a782c9a9c | ||
|
|
1b180d5491 | ||
|
|
8615e7da20 | ||
|
|
1488f0d96b | ||
|
|
c80fa5c415 | ||
|
|
62c5683c2e | ||
|
|
8d11a2abf3 | ||
|
|
09fa1d22f5 | ||
|
|
7c26a59ac0 | ||
|
|
7041528bde | ||
|
|
1df90faf92 | ||
|
|
6290ba1dc2 | ||
|
|
f5c563a074 | ||
|
|
a6a4f48b14 | ||
|
|
b082ad5eb9 | ||
|
|
c12fad2d22 | ||
|
|
254bc6635c | ||
|
|
c6a39a69b2 | ||
|
|
1f7da07a3e | ||
|
|
ba858b6625 | ||
|
|
c5f7c56644 | ||
|
|
78ab7ca107 | ||
|
|
dba0375988 | ||
|
|
7a55ede5e7 | ||
|
|
9a59256c8e | ||
|
|
f2b324933f | ||
|
|
3c2de37acc | ||
|
|
da93d55e32 | ||
|
|
99ce32f4a2 | ||
|
|
60e68967e5 | ||
|
|
740b8fd425 | ||
|
|
5fa528f552 | ||
|
|
3fa3161aa9 | ||
|
|
5acab3d8a9 | ||
|
|
bf81e2a79a | ||
|
|
81cc9c3f62 | ||
|
|
21705a1deb | ||
|
|
8ea40b5506 | ||
|
|
651299a6ca | ||
|
|
c96f93d894 | ||
|
|
792f0db6ba | ||
|
|
4de30256c3 | ||
|
|
01b0b9d8ef | ||
|
|
7d4e62942f | ||
|
|
0472d9296a | ||
|
|
8735d185b3 | ||
|
|
bbeed86bde | ||
|
|
c23fe08e05 |
51 changed files with 2708 additions and 685 deletions
1
.envrc
1
.envrc
|
|
@ -1,2 +1,3 @@
|
||||||
export VIRTUAL_ENV=venv
|
export VIRTUAL_ENV=venv
|
||||||
layout python
|
layout python
|
||||||
|
python -c 'import pyparsing' 2>/dev/null || pip install -r scripts/requirements.txt
|
||||||
|
|
|
||||||
12
.github/workflows/install_nvim.sh
vendored
12
.github/workflows/install_nvim.sh
vendored
|
|
@ -1,12 +1,16 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
PLUGINS="$HOME/.local/share/nvim/site/pack/plugins/start"
|
version="${NVIM_TAG-stable}"
|
||||||
mkdir -p "$PLUGINS"
|
dl_name="nvim-linux-x86_64.appimage"
|
||||||
|
# The appimage name changed in v0.10.4
|
||||||
wget "https://github.com/neovim/neovim/releases/download/${NVIM_TAG-stable}/nvim.appimage"
|
if python -c 'from packaging.version import Version; import sys; sys.exit(not (Version(sys.argv[1]) < Version("v0.10.4")))' "$version" 2>/dev/null; then
|
||||||
|
dl_name="nvim.appimage"
|
||||||
|
fi
|
||||||
|
curl -sL "https://github.com/neovim/neovim/releases/download/${version}/${dl_name}" -o nvim.appimage
|
||||||
chmod +x nvim.appimage
|
chmod +x nvim.appimage
|
||||||
./nvim.appimage --appimage-extract >/dev/null
|
./nvim.appimage --appimage-extract >/dev/null
|
||||||
rm -f nvim.appimage
|
rm -f nvim.appimage
|
||||||
mkdir -p ~/.local/share/nvim
|
mkdir -p ~/.local/share/nvim
|
||||||
mv squashfs-root ~/.local/share/nvim/appimage
|
mv squashfs-root ~/.local/share/nvim/appimage
|
||||||
sudo ln -s "$HOME/.local/share/nvim/appimage/AppRun" /usr/bin/nvim
|
sudo ln -s "$HOME/.local/share/nvim/appimage/AppRun" /usr/bin/nvim
|
||||||
|
/usr/bin/nvim --version
|
||||||
|
|
|
||||||
6
.github/workflows/tests.yml
vendored
6
.github/workflows/tests.yml
vendored
|
|
@ -4,6 +4,7 @@ on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
- stevearc-*
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
|
@ -34,7 +35,7 @@ jobs:
|
||||||
uses: JohnnyMorganz/stylua-action@v4
|
uses: JohnnyMorganz/stylua-action@v4
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
version: v0.20.0
|
version: v2.0.2
|
||||||
args: --check lua tests
|
args: --check lua tests
|
||||||
|
|
||||||
typecheck:
|
typecheck:
|
||||||
|
|
@ -52,7 +53,8 @@ jobs:
|
||||||
include:
|
include:
|
||||||
- nvim_tag: v0.8.3
|
- nvim_tag: v0.8.3
|
||||||
- nvim_tag: v0.9.4
|
- nvim_tag: v0.9.4
|
||||||
- nvim_tag: v0.10.0
|
- nvim_tag: v0.10.4
|
||||||
|
- nvim_tag: v0.11.0
|
||||||
|
|
||||||
name: Run tests
|
name: Run tests
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
|
|
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -48,3 +48,6 @@ venv/
|
||||||
doc/tags
|
doc/tags
|
||||||
scripts/nvim_doc_tools
|
scripts/nvim_doc_tools
|
||||||
scripts/nvim-typecheck-action
|
scripts/nvim-typecheck-action
|
||||||
|
scripts/benchmark.nvim
|
||||||
|
perf/tmp/
|
||||||
|
profile.json
|
||||||
|
|
|
||||||
63
CHANGELOG.md
63
CHANGELOG.md
|
|
@ -1,5 +1,68 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [2.15.0](https://github.com/stevearc/oil.nvim/compare/v2.14.0...v2.15.0) (2025-02-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add support for bufnr in column rendering functions ([#575](https://github.com/stevearc/oil.nvim/issues/575)) ([8abc58b](https://github.com/stevearc/oil.nvim/commit/8abc58b038f84078121ab1cac6ecad0163fe1635))
|
||||||
|
* API to automatically open preview window after opening oil ([#339](https://github.com/stevearc/oil.nvim/issues/339)) ([57528bf](https://github.com/stevearc/oil.nvim/commit/57528bf9c58080ca891e8d362d0a578895c136ce))
|
||||||
|
* can selectively add entries to quickfix ([#564](https://github.com/stevearc/oil.nvim/issues/564)) ([b594b9a](https://github.com/stevearc/oil.nvim/commit/b594b9a9052618669ccf6520b2d0c0d942eb8118))
|
||||||
|
* floating window max width/height can be percentages ([#553](https://github.com/stevearc/oil.nvim/issues/553)) ([1df90fa](https://github.com/stevearc/oil.nvim/commit/1df90faf927e78f5aacf278abd0bfdcb5f45e825))
|
||||||
|
* most moves and copies will copy the undofile ([#583](https://github.com/stevearc/oil.nvim/issues/583)) ([32dd3e3](https://github.com/stevearc/oil.nvim/commit/32dd3e378d47673679e76a773451f82f971a66df))
|
||||||
|
* pass oil bufnr to custom filename highlight function ([#552](https://github.com/stevearc/oil.nvim/issues/552)) ([f5c563a](https://github.com/stevearc/oil.nvim/commit/f5c563a074a38cee5a09f98e98b74dcd2c322490))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* crash in preview on nvim 0.8 ([81b2c5f](https://github.com/stevearc/oil.nvim/commit/81b2c5f04ae24a8c83b20ecbd017fecac15faca0))
|
||||||
|
* directory rendering with custom highlights ([#551](https://github.com/stevearc/oil.nvim/issues/551)) ([a6a4f48](https://github.com/stevearc/oil.nvim/commit/a6a4f48b14b4a51fded531c86f6c04b4503a2ef8))
|
||||||
|
* disable_preview respected when preview_method != "load" ([#577](https://github.com/stevearc/oil.nvim/issues/577)) ([7cde5aa](https://github.com/stevearc/oil.nvim/commit/7cde5aab10f564408e9ac349d457d755422d58cd))
|
||||||
|
* error when non-current oil buffer has validation errors ([#561](https://github.com/stevearc/oil.nvim/issues/561)) ([8d11a2a](https://github.com/stevearc/oil.nvim/commit/8d11a2abf3039b1974d4acd65fbc83ada2ca1084))
|
||||||
|
* gracefully handle fs_stat failures ([#558](https://github.com/stevearc/oil.nvim/issues/558)) ([7c26a59](https://github.com/stevearc/oil.nvim/commit/7c26a59ac0061b199bf9f44b19d45cfadd9b14f5))
|
||||||
|
* guard against nil metadata values ([#548](https://github.com/stevearc/oil.nvim/issues/548)) ([254bc66](https://github.com/stevearc/oil.nvim/commit/254bc6635cb3f77e6e9a89155652f368e5535160))
|
||||||
|
* more consistent cursor position when entering a new directory ([#536](https://github.com/stevearc/oil.nvim/issues/536)) ([c80fa5c](https://github.com/stevearc/oil.nvim/commit/c80fa5c415b882c1c694a32748cea09b7dafc2c5))
|
||||||
|
* more robust parsing of custom column timestamp formats ([#582](https://github.com/stevearc/oil.nvim/issues/582)) ([5313690](https://github.com/stevearc/oil.nvim/commit/5313690956d27cc6b53d5a2583df05e717c59b16))
|
||||||
|
* open files in correct window from floating oil ([#560](https://github.com/stevearc/oil.nvim/issues/560)) ([83ac518](https://github.com/stevearc/oil.nvim/commit/83ac5185f79ab8d869bccea792dc516ad02ad06e))
|
||||||
|
* preview sometimes causes oil buffers to be stuck in unloaded state ([#563](https://github.com/stevearc/oil.nvim/issues/563)) ([1488f0d](https://github.com/stevearc/oil.nvim/commit/1488f0d96b1cb820dd12f05a7bf5283a631a7c4d))
|
||||||
|
* stat files if fs_readdir doesn't provide a type ([#543](https://github.com/stevearc/oil.nvim/issues/543)) ([c6a39a6](https://github.com/stevearc/oil.nvim/commit/c6a39a69b2df7c10466f150dde0bd23e49c1fba3))
|
||||||
|
* support permissions checks on windows and virtual filesystems ([#555](https://github.com/stevearc/oil.nvim/issues/555)) ([7041528](https://github.com/stevearc/oil.nvim/commit/7041528bdedb350ad66e650684deec8456e053cc))
|
||||||
|
* work around incorrect link detection on windows ([#557](https://github.com/stevearc/oil.nvim/issues/557)) ([09fa1d2](https://github.com/stevearc/oil.nvim/commit/09fa1d22f5edf0730824d2b222d726c8c81bbdc9))
|
||||||
|
|
||||||
|
## [2.14.0](https://github.com/stevearc/oil.nvim/compare/v2.13.0...v2.14.0) (2024-12-21)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add `win_options` to `preview_win` ([#514](https://github.com/stevearc/oil.nvim/issues/514)) ([bbeed86](https://github.com/stevearc/oil.nvim/commit/bbeed86bde134da8d09bed64b6aa0d65642e6b23))
|
||||||
|
* add highlight group for orphaned links ([#502](https://github.com/stevearc/oil.nvim/issues/502)) ([740b8fd](https://github.com/stevearc/oil.nvim/commit/740b8fd425a2b77f7f40eb5ac155ebe66ff9515c))
|
||||||
|
* better merging of action desc when overriding keymaps ([f2b3249](https://github.com/stevearc/oil.nvim/commit/f2b324933f4d505cff6f7d445fd61fad02dcd9ae))
|
||||||
|
* config option to customize filename highlight group ([#508](https://github.com/stevearc/oil.nvim/issues/508)) ([99ce32f](https://github.com/stevearc/oil.nvim/commit/99ce32f4a2ecf76263b72fcc31efb163faa1a941))
|
||||||
|
* config option to disable previewing a file ([3fa3161](https://github.com/stevearc/oil.nvim/commit/3fa3161aa9515ff6a7cf7e44458b6a2114262870))
|
||||||
|
* disable preview for large files ([#511](https://github.com/stevearc/oil.nvim/issues/511)) ([c23fe08](https://github.com/stevearc/oil.nvim/commit/c23fe08e0546d9efc242e19f0d829efa7e7b2743))
|
||||||
|
* highlight groups for hidden files ([#459](https://github.com/stevearc/oil.nvim/issues/459)) ([60e6896](https://github.com/stevearc/oil.nvim/commit/60e68967e51ff1ecd264c29e3de0d52bfff22df3))
|
||||||
|
* option to quite vim if oil is closed as last buffer ([#491](https://github.com/stevearc/oil.nvim/issues/491)) ([81cc9c3](https://github.com/stevearc/oil.nvim/commit/81cc9c3f62ddbef3687931d119e505643496fa0a))
|
||||||
|
* use scratch buffer for file previews ([#467](https://github.com/stevearc/oil.nvim/issues/467)) ([21705a1](https://github.com/stevearc/oil.nvim/commit/21705a1debe6d85a53c138ab944484b685432b2b))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* cursor sometimes does not hover previous file ([8ea40b5](https://github.com/stevearc/oil.nvim/commit/8ea40b5506115b6d355e304dd9ee5089f7d78601))
|
||||||
|
* don't take over the preview window until it's opened for oil ([#532](https://github.com/stevearc/oil.nvim/issues/532)) ([78ab7ca](https://github.com/stevearc/oil.nvim/commit/78ab7ca1073731ebdf82efa474202defa028d5a4))
|
||||||
|
* handle files with newlines in the name ([#534](https://github.com/stevearc/oil.nvim/issues/534)) ([dba0375](https://github.com/stevearc/oil.nvim/commit/dba037598843973b8c54bc5ce0318db4a0da439d))
|
||||||
|
* image.nvim previews with preview_method=scratch ([5acab3d](https://github.com/stevearc/oil.nvim/commit/5acab3d8a9bc85a571688db432f2702dd7d901a4))
|
||||||
|
* improper file name escaping ([#530](https://github.com/stevearc/oil.nvim/issues/530)) ([7a55ede](https://github.com/stevearc/oil.nvim/commit/7a55ede5e745e31ea8e4cb5483221524922294bf))
|
||||||
|
* set alternate when using floating windows ([#526](https://github.com/stevearc/oil.nvim/issues/526)) ([c5f7c56](https://github.com/stevearc/oil.nvim/commit/c5f7c56644425e2b77e71904da98cda0331b3342))
|
||||||
|
* work around performance issue with treesitter, folds, and large directories ([da93d55](https://github.com/stevearc/oil.nvim/commit/da93d55e32d73a17c447067d168d80290ae96590))
|
||||||
|
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* change default view_options.natural_order behavior to disable on large directories ([01b0b9d](https://github.com/stevearc/oil.nvim/commit/01b0b9d8ef79b7b631e92f6b5fed1c639262d570))
|
||||||
|
* only sort entries after we have them all ([792f0db](https://github.com/stevearc/oil.nvim/commit/792f0db6ba8b626b14bc127e1ce7247185b3be91))
|
||||||
|
* optimize rendering cadence ([c96f93d](https://github.com/stevearc/oil.nvim/commit/c96f93d894cc97e76b0871bec4058530eee8ece4))
|
||||||
|
* replace vim.endswith and vim.startswith with string.match ([4de3025](https://github.com/stevearc/oil.nvim/commit/4de30256c32cd272482bc6df0c6de78ffc389153))
|
||||||
|
|
||||||
## [2.13.0](https://github.com/stevearc/oil.nvim/compare/v2.12.2...v2.13.0) (2024-11-11)
|
## [2.13.0](https://github.com/stevearc/oil.nvim/compare/v2.12.2...v2.13.0) (2024-11-11)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
21
Makefile
21
Makefile
|
|
@ -35,13 +35,32 @@ fastlint: scripts/nvim_doc_tools venv
|
||||||
luacheck lua tests --formatter plain
|
luacheck lua tests --formatter plain
|
||||||
stylua --check lua tests
|
stylua --check lua tests
|
||||||
|
|
||||||
|
## profile: use LuaJIT profiler to profile the plugin
|
||||||
|
.PHONY: profile
|
||||||
|
profile: scripts/benchmark.nvim
|
||||||
|
nvim --clean -u perf/bootstrap.lua -c 'lua jit_profile()'
|
||||||
|
|
||||||
|
## flame_profile: create a trace in the chrome profiler format
|
||||||
|
.PHONY: flame_profile
|
||||||
|
flame_profile: scripts/benchmark.nvim
|
||||||
|
nvim --clean -u perf/bootstrap.lua -c 'lua flame_profile()'
|
||||||
|
|
||||||
|
## benchmark: benchmark performance opening directory with many files
|
||||||
|
.PHONY: benchmark
|
||||||
|
benchmark: scripts/benchmark.nvim
|
||||||
|
nvim --clean -u perf/bootstrap.lua -c 'lua benchmark()'
|
||||||
|
@cat perf/tmp/benchmark.txt
|
||||||
|
|
||||||
scripts/nvim_doc_tools:
|
scripts/nvim_doc_tools:
|
||||||
git clone https://github.com/stevearc/nvim_doc_tools scripts/nvim_doc_tools
|
git clone https://github.com/stevearc/nvim_doc_tools scripts/nvim_doc_tools
|
||||||
|
|
||||||
scripts/nvim-typecheck-action:
|
scripts/nvim-typecheck-action:
|
||||||
git clone https://github.com/stevearc/nvim-typecheck-action scripts/nvim-typecheck-action
|
git clone https://github.com/stevearc/nvim-typecheck-action scripts/nvim-typecheck-action
|
||||||
|
|
||||||
|
scripts/benchmark.nvim:
|
||||||
|
git clone https://github.com/stevearc/benchmark.nvim scripts/benchmark.nvim
|
||||||
|
|
||||||
## clean: reset the repository to a clean state
|
## clean: reset the repository to a clean state
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
rm -rf scripts/nvim_doc_tools scripts/nvim-typecheck-action venv .testenv
|
rm -rf scripts/nvim_doc_tools scripts/nvim-typecheck-action venv .testenv perf/tmp profile.json
|
||||||
|
|
|
||||||
97
README.md
97
README.md
|
|
@ -12,6 +12,7 @@ https://user-images.githubusercontent.com/506791/209727111-6b4a11f4-634a-4efa-94
|
||||||
- [Options](#options)
|
- [Options](#options)
|
||||||
- [Adapters](#adapters)
|
- [Adapters](#adapters)
|
||||||
- [Recipes](#recipes)
|
- [Recipes](#recipes)
|
||||||
|
- [Third-party extensions](#third-party-extensions)
|
||||||
- [API](#api)
|
- [API](#api)
|
||||||
- [FAQ](#faq)
|
- [FAQ](#faq)
|
||||||
|
|
||||||
|
|
@ -21,7 +22,7 @@ https://user-images.githubusercontent.com/506791/209727111-6b4a11f4-634a-4efa-94
|
||||||
|
|
||||||
- Neovim 0.8+
|
- Neovim 0.8+
|
||||||
- Icon provider plugin (optional)
|
- Icon provider plugin (optional)
|
||||||
- [mini.icons](https://github.com/echasnovski/mini.nvim/blob/main/readmes/mini-icons.md) for file and folder icons
|
- [mini.icons](https://github.com/nvim-mini/mini.nvim/blob/main/readmes/mini-icons.md) for file and folder icons
|
||||||
- [nvim-web-devicons](https://github.com/nvim-tree/nvim-web-devicons) for file icons
|
- [nvim-web-devicons](https://github.com/nvim-tree/nvim-web-devicons) for file icons
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
@ -38,8 +39,10 @@ oil.nvim supports all the usual plugin managers
|
||||||
---@type oil.SetupOpts
|
---@type oil.SetupOpts
|
||||||
opts = {},
|
opts = {},
|
||||||
-- Optional dependencies
|
-- Optional dependencies
|
||||||
dependencies = { { "echasnovski/mini.icons", opts = {} } },
|
dependencies = { { "nvim-mini/mini.icons", opts = {} } },
|
||||||
-- dependencies = { "nvim-tree/nvim-web-devicons" }, -- use if prefer nvim-web-devicons
|
-- dependencies = { "nvim-tree/nvim-web-devicons" }, -- use if you prefer nvim-web-devicons
|
||||||
|
-- Lazy loading is not recommended because it is very tricky to make it work correctly in all situations.
|
||||||
|
lazy = false,
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -190,22 +193,22 @@ require("oil").setup({
|
||||||
-- Set to `false` to remove a keymap
|
-- Set to `false` to remove a keymap
|
||||||
-- See :help oil-actions for a list of all available actions
|
-- See :help oil-actions for a list of all available actions
|
||||||
keymaps = {
|
keymaps = {
|
||||||
["g?"] = "actions.show_help",
|
["g?"] = { "actions.show_help", mode = "n" },
|
||||||
["<CR>"] = "actions.select",
|
["<CR>"] = "actions.select",
|
||||||
["<C-s>"] = { "actions.select", opts = { vertical = true }, desc = "Open the entry in a vertical split" },
|
["<C-s>"] = { "actions.select", opts = { vertical = true } },
|
||||||
["<C-h>"] = { "actions.select", opts = { horizontal = true }, desc = "Open the entry in a horizontal split" },
|
["<C-h>"] = { "actions.select", opts = { horizontal = true } },
|
||||||
["<C-t>"] = { "actions.select", opts = { tab = true }, desc = "Open the entry in new tab" },
|
["<C-t>"] = { "actions.select", opts = { tab = true } },
|
||||||
["<C-p>"] = "actions.preview",
|
["<C-p>"] = "actions.preview",
|
||||||
["<C-c>"] = "actions.close",
|
["<C-c>"] = { "actions.close", mode = "n" },
|
||||||
["<C-l>"] = "actions.refresh",
|
["<C-l>"] = "actions.refresh",
|
||||||
["-"] = "actions.parent",
|
["-"] = { "actions.parent", mode = "n" },
|
||||||
["_"] = "actions.open_cwd",
|
["_"] = { "actions.open_cwd", mode = "n" },
|
||||||
["`"] = "actions.cd",
|
["`"] = { "actions.cd", mode = "n" },
|
||||||
["~"] = { "actions.cd", opts = { scope = "tab" }, desc = ":tcd to the current oil directory", mode = "n" },
|
["g~"] = { "actions.cd", opts = { scope = "tab" }, mode = "n" },
|
||||||
["gs"] = "actions.change_sort",
|
["gs"] = { "actions.change_sort", mode = "n" },
|
||||||
["gx"] = "actions.open_external",
|
["gx"] = "actions.open_external",
|
||||||
["g."] = "actions.toggle_hidden",
|
["g."] = { "actions.toggle_hidden", mode = "n" },
|
||||||
["g\\"] = "actions.toggle_trash",
|
["g\\"] = { "actions.toggle_trash", mode = "n" },
|
||||||
},
|
},
|
||||||
-- Set to false to disable all of the above keymaps
|
-- Set to false to disable all of the above keymaps
|
||||||
use_default_keymaps = true,
|
use_default_keymaps = true,
|
||||||
|
|
@ -214,15 +217,16 @@ require("oil").setup({
|
||||||
show_hidden = false,
|
show_hidden = false,
|
||||||
-- This function defines what is considered a "hidden" file
|
-- This function defines what is considered a "hidden" file
|
||||||
is_hidden_file = function(name, bufnr)
|
is_hidden_file = function(name, bufnr)
|
||||||
return vim.startswith(name, ".")
|
local m = name:match("^%.")
|
||||||
|
return m ~= nil
|
||||||
end,
|
end,
|
||||||
-- This function defines what will never be shown, even when `show_hidden` is set
|
-- This function defines what will never be shown, even when `show_hidden` is set
|
||||||
is_always_hidden = function(name, bufnr)
|
is_always_hidden = function(name, bufnr)
|
||||||
return false
|
return false
|
||||||
end,
|
end,
|
||||||
-- Sort file names in a more intuitive order for humans. Is less performant,
|
-- Sort file names with numbers in a more intuitive order for humans.
|
||||||
-- so you may want to set to false if you work with large directories.
|
-- Can be "fast", true, or false. "fast" will turn it off for large directories.
|
||||||
natural_order = true,
|
natural_order = "fast",
|
||||||
-- Sort file and directory names case insensitive
|
-- Sort file and directory names case insensitive
|
||||||
case_insensitive = false,
|
case_insensitive = false,
|
||||||
sort = {
|
sort = {
|
||||||
|
|
@ -231,9 +235,15 @@ require("oil").setup({
|
||||||
{ "type", "asc" },
|
{ "type", "asc" },
|
||||||
{ "name", "asc" },
|
{ "name", "asc" },
|
||||||
},
|
},
|
||||||
|
-- Customize the highlight group for the file name
|
||||||
|
highlight_filename = function(entry, is_hidden, is_link_target, is_link_orphan)
|
||||||
|
return nil
|
||||||
|
end,
|
||||||
},
|
},
|
||||||
-- Extra arguments to pass to SCP when moving/copying files over SSH
|
-- Extra arguments to pass to SCP when moving/copying files over SSH
|
||||||
extra_scp_args = {},
|
extra_scp_args = {},
|
||||||
|
-- Extra arguments to pass to aws s3 when creating/deleting/moving/copying files using aws s3
|
||||||
|
extra_s3_args = {},
|
||||||
-- EXPERIMENTAL support for performing file operations with git
|
-- EXPERIMENTAL support for performing file operations with git
|
||||||
git = {
|
git = {
|
||||||
-- Return true to automatically git add/mv/rm files
|
-- Return true to automatically git add/mv/rm files
|
||||||
|
|
@ -251,9 +261,10 @@ require("oil").setup({
|
||||||
float = {
|
float = {
|
||||||
-- Padding around the floating window
|
-- Padding around the floating window
|
||||||
padding = 2,
|
padding = 2,
|
||||||
|
-- max_width and max_height can be integers or a float between 0 and 1 (e.g. 0.4 for 40%)
|
||||||
max_width = 0,
|
max_width = 0,
|
||||||
max_height = 0,
|
max_height = 0,
|
||||||
border = "rounded",
|
border = nil,
|
||||||
win_options = {
|
win_options = {
|
||||||
winblend = 0,
|
winblend = 0,
|
||||||
},
|
},
|
||||||
|
|
@ -271,6 +282,14 @@ require("oil").setup({
|
||||||
preview_win = {
|
preview_win = {
|
||||||
-- Whether the preview window is automatically updated when the cursor is moved
|
-- Whether the preview window is automatically updated when the cursor is moved
|
||||||
update_on_cursor_moved = true,
|
update_on_cursor_moved = true,
|
||||||
|
-- How to open the preview window "load"|"scratch"|"fast_scratch"
|
||||||
|
preview_method = "fast_scratch",
|
||||||
|
-- A function that returns true to disable preview on a file e.g. to avoid lag
|
||||||
|
disable_preview = function(filename)
|
||||||
|
return false
|
||||||
|
end,
|
||||||
|
-- Window-local options to use for preview window buffers
|
||||||
|
win_options = {},
|
||||||
},
|
},
|
||||||
-- Configuration for the floating action confirmation window
|
-- Configuration for the floating action confirmation window
|
||||||
confirmation = {
|
confirmation = {
|
||||||
|
|
@ -290,7 +309,7 @@ require("oil").setup({
|
||||||
min_height = { 5, 0.1 },
|
min_height = { 5, 0.1 },
|
||||||
-- optionally define an integer/float for the exact height of the preview window
|
-- optionally define an integer/float for the exact height of the preview window
|
||||||
height = nil,
|
height = nil,
|
||||||
border = "rounded",
|
border = nil,
|
||||||
win_options = {
|
win_options = {
|
||||||
winblend = 0,
|
winblend = 0,
|
||||||
},
|
},
|
||||||
|
|
@ -303,7 +322,7 @@ require("oil").setup({
|
||||||
max_height = { 10, 0.9 },
|
max_height = { 10, 0.9 },
|
||||||
min_height = { 5, 0.1 },
|
min_height = { 5, 0.1 },
|
||||||
height = nil,
|
height = nil,
|
||||||
border = "rounded",
|
border = nil,
|
||||||
minimized_border = "none",
|
minimized_border = "none",
|
||||||
win_options = {
|
win_options = {
|
||||||
winblend = 0,
|
winblend = 0,
|
||||||
|
|
@ -311,11 +330,11 @@ require("oil").setup({
|
||||||
},
|
},
|
||||||
-- Configuration for the floating SSH window
|
-- Configuration for the floating SSH window
|
||||||
ssh = {
|
ssh = {
|
||||||
border = "rounded",
|
border = nil,
|
||||||
},
|
},
|
||||||
-- Configuration for the floating keymaps help window
|
-- Configuration for the floating keymaps help window
|
||||||
keymaps_help = {
|
keymaps_help = {
|
||||||
border = "rounded",
|
border = nil,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
@ -338,12 +357,30 @@ This may look familiar. In fact, this is the same url format that netrw uses.
|
||||||
|
|
||||||
Note that at the moment the ssh adapter does not support Windows machines, and it requires the server to have a `/bin/sh` binary as well as standard unix commands (`ls`, `rm`, `mv`, `mkdir`, `chmod`, `cp`, `touch`, `ln`, `echo`).
|
Note that at the moment the ssh adapter does not support Windows machines, and it requires the server to have a `/bin/sh` binary as well as standard unix commands (`ls`, `rm`, `mv`, `mkdir`, `chmod`, `cp`, `touch`, `ln`, `echo`).
|
||||||
|
|
||||||
|
### S3
|
||||||
|
|
||||||
|
This adapter allows you to browse files stored in aws s3. To use it, make sure `aws` is setup correctly and then simply open a buffer using the following name template:
|
||||||
|
|
||||||
|
```
|
||||||
|
nvim oil-s3://[bucket]/[path]
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that older versions of Neovim don't support numbers in the url, so for Neovim 0.11 and older the url starts with `oil-sss`.
|
||||||
|
|
||||||
## Recipes
|
## Recipes
|
||||||
|
|
||||||
- [Toggle file detail view](doc/recipes.md#toggle-file-detail-view)
|
- [Toggle file detail view](doc/recipes.md#toggle-file-detail-view)
|
||||||
- [Show CWD in the winbar](doc/recipes.md#show-cwd-in-the-winbar)
|
- [Show CWD in the winbar](doc/recipes.md#show-cwd-in-the-winbar)
|
||||||
- [Hide gitignored files and show git tracked hidden files](doc/recipes.md#hide-gitignored-files-and-show-git-tracked-hidden-files)
|
- [Hide gitignored files and show git tracked hidden files](doc/recipes.md#hide-gitignored-files-and-show-git-tracked-hidden-files)
|
||||||
|
|
||||||
|
## Third-party extensions
|
||||||
|
|
||||||
|
These are plugins maintained by other authors that extend the functionality of oil.nvim.
|
||||||
|
|
||||||
|
- [oil-git-status.nvim](https://github.com/refractalize/oil-git-status.nvim) - Shows git status of files in statuscolumn
|
||||||
|
- [oil-git.nvim](https://github.com/benomahony/oil-git.nvim) - Shows git status of files with colour and symbols
|
||||||
|
- [oil-lsp-diagnostics.nvim](https://github.com/JezerM/oil-lsp-diagnostics.nvim) - Shows LSP diagnostics indicator as virtual text
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
<!-- API -->
|
<!-- API -->
|
||||||
|
|
@ -356,10 +393,10 @@ Note that at the moment the ssh adapter does not support Windows machines, and i
|
||||||
- [set_is_hidden_file(is_hidden_file)](doc/api.md#set_is_hidden_fileis_hidden_file)
|
- [set_is_hidden_file(is_hidden_file)](doc/api.md#set_is_hidden_fileis_hidden_file)
|
||||||
- [toggle_hidden()](doc/api.md#toggle_hidden)
|
- [toggle_hidden()](doc/api.md#toggle_hidden)
|
||||||
- [get_current_dir(bufnr)](doc/api.md#get_current_dirbufnr)
|
- [get_current_dir(bufnr)](doc/api.md#get_current_dirbufnr)
|
||||||
- [open_float(dir)](doc/api.md#open_floatdir)
|
- [open_float(dir, opts, cb)](doc/api.md#open_floatdir-opts-cb)
|
||||||
- [toggle_float(dir)](doc/api.md#toggle_floatdir)
|
- [toggle_float(dir, opts, cb)](doc/api.md#toggle_floatdir-opts-cb)
|
||||||
- [open(dir)](doc/api.md#opendir)
|
- [open(dir, opts, cb)](doc/api.md#opendir-opts-cb)
|
||||||
- [close()](doc/api.md#close)
|
- [close(opts)](doc/api.md#closeopts)
|
||||||
- [open_preview(opts, callback)](doc/api.md#open_previewopts-callback)
|
- [open_preview(opts, callback)](doc/api.md#open_previewopts-callback)
|
||||||
- [select(opts, callback)](doc/api.md#selectopts-callback)
|
- [select(opts, callback)](doc/api.md#selectopts-callback)
|
||||||
- [save(opts, cb)](doc/api.md#saveopts-cb)
|
- [save(opts, cb)](doc/api.md#saveopts-cb)
|
||||||
|
|
@ -384,7 +421,7 @@ Plus, I think it's pretty slick ;)
|
||||||
|
|
||||||
- You like to use a netrw-like view to browse directories (as opposed to a file tree)
|
- You like to use a netrw-like view to browse directories (as opposed to a file tree)
|
||||||
- AND you want to be able to edit your filesystem like a buffer
|
- AND you want to be able to edit your filesystem like a buffer
|
||||||
- AND you want to perform cross-directory actions. AFAIK there is no other plugin that does this. (update: [mini.files](https://github.com/echasnovski/mini.nvim/blob/main/readmes/mini-files.md) also offers this functionality)
|
- AND you want to perform cross-directory actions. AFAIK there is no other plugin that does this. (update: [mini.files](https://github.com/nvim-mini/mini.nvim/blob/main/readmes/mini-files.md) also offers this functionality)
|
||||||
|
|
||||||
If you don't need those features specifically, check out the alternatives listed below
|
If you don't need those features specifically, check out the alternatives listed below
|
||||||
|
|
||||||
|
|
@ -400,7 +437,7 @@ If you don't need those features specifically, check out the alternatives listed
|
||||||
|
|
||||||
**A:**
|
**A:**
|
||||||
|
|
||||||
- [mini.files](https://github.com/echasnovski/mini.nvim/blob/main/readmes/mini-files.md): A newer plugin that also supports cross-directory filesystem-as-buffer edits. It utilizes a unique column view.
|
- [mini.files](https://github.com/nvim-mini/mini.nvim/blob/main/readmes/mini-files.md): A newer plugin that also supports cross-directory filesystem-as-buffer edits. It utilizes a unique column view.
|
||||||
- [vim-vinegar](https://github.com/tpope/vim-vinegar): The granddaddy. This made me fall in love with single-directory file browsing. I stopped using it when I encountered netrw bugs and performance issues.
|
- [vim-vinegar](https://github.com/tpope/vim-vinegar): The granddaddy. This made me fall in love with single-directory file browsing. I stopped using it when I encountered netrw bugs and performance issues.
|
||||||
- [defx.nvim](https://github.com/Shougo/defx.nvim): What I switched to after vim-vinegar. Much more flexible and performant, but requires python and the API is a little hard to work with.
|
- [defx.nvim](https://github.com/Shougo/defx.nvim): What I switched to after vim-vinegar. Much more flexible and performant, but requires python and the API is a little hard to work with.
|
||||||
- [dirbuf.nvim](https://github.com/elihunter173/dirbuf.nvim): The first plugin I encountered that let you edit the filesystem like a buffer. Never used it because it [can't do cross-directory edits](https://github.com/elihunter173/dirbuf.nvim/issues/7).
|
- [dirbuf.nvim](https://github.com/elihunter173/dirbuf.nvim): The first plugin I encountered that let you edit the filesystem like a buffer. Never used it because it [can't do cross-directory edits](https://github.com/elihunter173/dirbuf.nvim/issues/7).
|
||||||
|
|
|
||||||
55
doc/api.md
55
doc/api.md
|
|
@ -10,10 +10,10 @@
|
||||||
- [set_is_hidden_file(is_hidden_file)](#set_is_hidden_fileis_hidden_file)
|
- [set_is_hidden_file(is_hidden_file)](#set_is_hidden_fileis_hidden_file)
|
||||||
- [toggle_hidden()](#toggle_hidden)
|
- [toggle_hidden()](#toggle_hidden)
|
||||||
- [get_current_dir(bufnr)](#get_current_dirbufnr)
|
- [get_current_dir(bufnr)](#get_current_dirbufnr)
|
||||||
- [open_float(dir)](#open_floatdir)
|
- [open_float(dir, opts, cb)](#open_floatdir-opts-cb)
|
||||||
- [toggle_float(dir)](#toggle_floatdir)
|
- [toggle_float(dir, opts, cb)](#toggle_floatdir-opts-cb)
|
||||||
- [open(dir)](#opendir)
|
- [open(dir, opts, cb)](#opendir-opts-cb)
|
||||||
- [close()](#close)
|
- [close(opts)](#closeopts)
|
||||||
- [open_preview(opts, callback)](#open_previewopts-callback)
|
- [open_preview(opts, callback)](#open_previewopts-callback)
|
||||||
- [select(opts, callback)](#selectopts-callback)
|
- [select(opts, callback)](#selectopts-callback)
|
||||||
- [save(opts, cb)](#saveopts-cb)
|
- [save(opts, cb)](#saveopts-cb)
|
||||||
|
|
@ -92,38 +92,60 @@ Get the current directory
|
||||||
| ----- | -------------- | ---- |
|
| ----- | -------------- | ---- |
|
||||||
| bufnr | `nil\|integer` | |
|
| bufnr | `nil\|integer` | |
|
||||||
|
|
||||||
## open_float(dir)
|
## open_float(dir, opts, cb)
|
||||||
|
|
||||||
`open_float(dir)` \
|
`open_float(dir, opts, cb)` \
|
||||||
Open oil browser in a floating window
|
Open oil browser in a floating window
|
||||||
|
|
||||||
| Param | Type | Desc |
|
| Param | Type | Desc |
|
||||||
| ----- | ------------- | ------------------------------------------------------------------------------------------- |
|
| ------------ | ------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
|
||||||
| dir | `nil\|string` | When nil, open the parent of the current buffer, or the cwd if current buffer is not a file |
|
| dir | `nil\|string` | When nil, open the parent of the current buffer, or the cwd if current buffer is not a file |
|
||||||
|
| opts | `nil\|oil.OpenOpts` | |
|
||||||
|
| >preview | `nil\|oil.OpenPreviewOpts` | When present, open the preview window after opening oil |
|
||||||
|
| >>vertical | `nil\|boolean` | Open the buffer in a vertical split |
|
||||||
|
| >>horizontal | `nil\|boolean` | Open the buffer in a horizontal split |
|
||||||
|
| >>split | `nil\|"aboveleft"\|"belowright"\|"topleft"\|"botright"` | Split modifier |
|
||||||
|
| cb | `nil\|fun()` | Called after the oil buffer is ready |
|
||||||
|
|
||||||
## toggle_float(dir)
|
## toggle_float(dir, opts, cb)
|
||||||
|
|
||||||
`toggle_float(dir)` \
|
`toggle_float(dir, opts, cb)` \
|
||||||
Open oil browser in a floating window, or close it if open
|
Open oil browser in a floating window, or close it if open
|
||||||
|
|
||||||
| Param | Type | Desc |
|
| Param | Type | Desc |
|
||||||
| ----- | ------------- | ------------------------------------------------------------------------------------------- |
|
| ------------ | ------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
|
||||||
| dir | `nil\|string` | When nil, open the parent of the current buffer, or the cwd if current buffer is not a file |
|
| dir | `nil\|string` | When nil, open the parent of the current buffer, or the cwd if current buffer is not a file |
|
||||||
|
| opts | `nil\|oil.OpenOpts` | |
|
||||||
|
| >preview | `nil\|oil.OpenPreviewOpts` | When present, open the preview window after opening oil |
|
||||||
|
| >>vertical | `nil\|boolean` | Open the buffer in a vertical split |
|
||||||
|
| >>horizontal | `nil\|boolean` | Open the buffer in a horizontal split |
|
||||||
|
| >>split | `nil\|"aboveleft"\|"belowright"\|"topleft"\|"botright"` | Split modifier |
|
||||||
|
| cb | `nil\|fun()` | Called after the oil buffer is ready |
|
||||||
|
|
||||||
## open(dir)
|
## open(dir, opts, cb)
|
||||||
|
|
||||||
`open(dir)` \
|
`open(dir, opts, cb)` \
|
||||||
Open oil browser for a directory
|
Open oil browser for a directory
|
||||||
|
|
||||||
| Param | Type | Desc |
|
| Param | Type | Desc |
|
||||||
| ----- | ------------- | ------------------------------------------------------------------------------------------- |
|
| ------------ | ------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
|
||||||
| dir | `nil\|string` | When nil, open the parent of the current buffer, or the cwd if current buffer is not a file |
|
| dir | `nil\|string` | When nil, open the parent of the current buffer, or the cwd if current buffer is not a file |
|
||||||
|
| opts | `nil\|oil.OpenOpts` | |
|
||||||
|
| >preview | `nil\|oil.OpenPreviewOpts` | When present, open the preview window after opening oil |
|
||||||
|
| >>vertical | `nil\|boolean` | Open the buffer in a vertical split |
|
||||||
|
| >>horizontal | `nil\|boolean` | Open the buffer in a horizontal split |
|
||||||
|
| >>split | `nil\|"aboveleft"\|"belowright"\|"topleft"\|"botright"` | Split modifier |
|
||||||
|
| cb | `nil\|fun()` | Called after the oil buffer is ready |
|
||||||
|
|
||||||
## close()
|
## close(opts)
|
||||||
|
|
||||||
`close()` \
|
`close(opts)` \
|
||||||
Restore the buffer that was present when oil was opened
|
Restore the buffer that was present when oil was opened
|
||||||
|
|
||||||
|
| Param | Type | Desc |
|
||||||
|
| ----------------- | -------------------- | --------------------------------------------------- |
|
||||||
|
| opts | `nil\|oil.CloseOpts` | |
|
||||||
|
| >exit_if_last_buf | `nil\|boolean` | Exit vim if this oil buffer is the last open buffer |
|
||||||
|
|
||||||
## open_preview(opts, callback)
|
## open_preview(opts, callback)
|
||||||
|
|
||||||
|
|
@ -144,13 +166,14 @@ Preview the entry under the cursor in a split
|
||||||
Select the entry under the cursor
|
Select the entry under the cursor
|
||||||
|
|
||||||
| Param | Type | Desc |
|
| Param | Type | Desc |
|
||||||
| ----------- | ------------------------------------------------------- | ---------------------------------------------------- |
|
| ----------------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| opts | `nil\|oil.SelectOpts` | |
|
| opts | `nil\|oil.SelectOpts` | |
|
||||||
| >vertical | `nil\|boolean` | Open the buffer in a vertical split |
|
| >vertical | `nil\|boolean` | Open the buffer in a vertical split |
|
||||||
| >horizontal | `nil\|boolean` | Open the buffer in a horizontal split |
|
| >horizontal | `nil\|boolean` | Open the buffer in a horizontal split |
|
||||||
| >split | `nil\|"aboveleft"\|"belowright"\|"topleft"\|"botright"` | Split modifier |
|
| >split | `nil\|"aboveleft"\|"belowright"\|"topleft"\|"botright"` | Split modifier |
|
||||||
| >tab | `nil\|boolean` | Open the buffer in a new tab |
|
| >tab | `nil\|boolean` | Open the buffer in a new tab |
|
||||||
| >close | `nil\|boolean` | Close the original oil buffer once selection is made |
|
| >close | `nil\|boolean` | Close the original oil buffer once selection is made |
|
||||||
|
| >handle_buffer_callback | `nil\|fun(buf_id: integer)` | If defined, all other buffer related options here would be ignored. This callback allows you to take over the process of opening the buffer yourself. |
|
||||||
| callback | `nil\|fun(err: nil\|string)` | Called once all entries have been opened |
|
| callback | `nil\|fun(err: nil\|string)` | Called once all entries have been opened |
|
||||||
|
|
||||||
## save(opts, cb)
|
## save(opts, cb)
|
||||||
|
|
|
||||||
184
doc/oil.txt
184
doc/oil.txt
|
|
@ -75,22 +75,22 @@ CONFIG *oil-confi
|
||||||
-- Set to `false` to remove a keymap
|
-- Set to `false` to remove a keymap
|
||||||
-- See :help oil-actions for a list of all available actions
|
-- See :help oil-actions for a list of all available actions
|
||||||
keymaps = {
|
keymaps = {
|
||||||
["g?"] = "actions.show_help",
|
["g?"] = { "actions.show_help", mode = "n" },
|
||||||
["<CR>"] = "actions.select",
|
["<CR>"] = "actions.select",
|
||||||
["<C-s>"] = { "actions.select", opts = { vertical = true }, desc = "Open the entry in a vertical split" },
|
["<C-s>"] = { "actions.select", opts = { vertical = true } },
|
||||||
["<C-h>"] = { "actions.select", opts = { horizontal = true }, desc = "Open the entry in a horizontal split" },
|
["<C-h>"] = { "actions.select", opts = { horizontal = true } },
|
||||||
["<C-t>"] = { "actions.select", opts = { tab = true }, desc = "Open the entry in new tab" },
|
["<C-t>"] = { "actions.select", opts = { tab = true } },
|
||||||
["<C-p>"] = "actions.preview",
|
["<C-p>"] = "actions.preview",
|
||||||
["<C-c>"] = "actions.close",
|
["<C-c>"] = { "actions.close", mode = "n" },
|
||||||
["<C-l>"] = "actions.refresh",
|
["<C-l>"] = "actions.refresh",
|
||||||
["-"] = "actions.parent",
|
["-"] = { "actions.parent", mode = "n" },
|
||||||
["_"] = "actions.open_cwd",
|
["_"] = { "actions.open_cwd", mode = "n" },
|
||||||
["`"] = "actions.cd",
|
["`"] = { "actions.cd", mode = "n" },
|
||||||
["~"] = { "actions.cd", opts = { scope = "tab" }, desc = ":tcd to the current oil directory", mode = "n" },
|
["g~"] = { "actions.cd", opts = { scope = "tab" }, mode = "n" },
|
||||||
["gs"] = "actions.change_sort",
|
["gs"] = { "actions.change_sort", mode = "n" },
|
||||||
["gx"] = "actions.open_external",
|
["gx"] = "actions.open_external",
|
||||||
["g."] = "actions.toggle_hidden",
|
["g."] = { "actions.toggle_hidden", mode = "n" },
|
||||||
["g\\"] = "actions.toggle_trash",
|
["g\\"] = { "actions.toggle_trash", mode = "n" },
|
||||||
},
|
},
|
||||||
-- Set to false to disable all of the above keymaps
|
-- Set to false to disable all of the above keymaps
|
||||||
use_default_keymaps = true,
|
use_default_keymaps = true,
|
||||||
|
|
@ -99,15 +99,16 @@ CONFIG *oil-confi
|
||||||
show_hidden = false,
|
show_hidden = false,
|
||||||
-- This function defines what is considered a "hidden" file
|
-- This function defines what is considered a "hidden" file
|
||||||
is_hidden_file = function(name, bufnr)
|
is_hidden_file = function(name, bufnr)
|
||||||
return vim.startswith(name, ".")
|
local m = name:match("^%.")
|
||||||
|
return m ~= nil
|
||||||
end,
|
end,
|
||||||
-- This function defines what will never be shown, even when `show_hidden` is set
|
-- This function defines what will never be shown, even when `show_hidden` is set
|
||||||
is_always_hidden = function(name, bufnr)
|
is_always_hidden = function(name, bufnr)
|
||||||
return false
|
return false
|
||||||
end,
|
end,
|
||||||
-- Sort file names in a more intuitive order for humans. Is less performant,
|
-- Sort file names with numbers in a more intuitive order for humans.
|
||||||
-- so you may want to set to false if you work with large directories.
|
-- Can be "fast", true, or false. "fast" will turn it off for large directories.
|
||||||
natural_order = true,
|
natural_order = "fast",
|
||||||
-- Sort file and directory names case insensitive
|
-- Sort file and directory names case insensitive
|
||||||
case_insensitive = false,
|
case_insensitive = false,
|
||||||
sort = {
|
sort = {
|
||||||
|
|
@ -116,9 +117,15 @@ CONFIG *oil-confi
|
||||||
{ "type", "asc" },
|
{ "type", "asc" },
|
||||||
{ "name", "asc" },
|
{ "name", "asc" },
|
||||||
},
|
},
|
||||||
|
-- Customize the highlight group for the file name
|
||||||
|
highlight_filename = function(entry, is_hidden, is_link_target, is_link_orphan)
|
||||||
|
return nil
|
||||||
|
end,
|
||||||
},
|
},
|
||||||
-- Extra arguments to pass to SCP when moving/copying files over SSH
|
-- Extra arguments to pass to SCP when moving/copying files over SSH
|
||||||
extra_scp_args = {},
|
extra_scp_args = {},
|
||||||
|
-- Extra arguments to pass to aws s3 when creating/deleting/moving/copying files using aws s3
|
||||||
|
extra_s3_args = {},
|
||||||
-- EXPERIMENTAL support for performing file operations with git
|
-- EXPERIMENTAL support for performing file operations with git
|
||||||
git = {
|
git = {
|
||||||
-- Return true to automatically git add/mv/rm files
|
-- Return true to automatically git add/mv/rm files
|
||||||
|
|
@ -136,9 +143,10 @@ CONFIG *oil-confi
|
||||||
float = {
|
float = {
|
||||||
-- Padding around the floating window
|
-- Padding around the floating window
|
||||||
padding = 2,
|
padding = 2,
|
||||||
|
-- max_width and max_height can be integers or a float between 0 and 1 (e.g. 0.4 for 40%)
|
||||||
max_width = 0,
|
max_width = 0,
|
||||||
max_height = 0,
|
max_height = 0,
|
||||||
border = "rounded",
|
border = nil,
|
||||||
win_options = {
|
win_options = {
|
||||||
winblend = 0,
|
winblend = 0,
|
||||||
},
|
},
|
||||||
|
|
@ -156,6 +164,14 @@ CONFIG *oil-confi
|
||||||
preview_win = {
|
preview_win = {
|
||||||
-- Whether the preview window is automatically updated when the cursor is moved
|
-- Whether the preview window is automatically updated when the cursor is moved
|
||||||
update_on_cursor_moved = true,
|
update_on_cursor_moved = true,
|
||||||
|
-- How to open the preview window "load"|"scratch"|"fast_scratch"
|
||||||
|
preview_method = "fast_scratch",
|
||||||
|
-- A function that returns true to disable preview on a file e.g. to avoid lag
|
||||||
|
disable_preview = function(filename)
|
||||||
|
return false
|
||||||
|
end,
|
||||||
|
-- Window-local options to use for preview window buffers
|
||||||
|
win_options = {},
|
||||||
},
|
},
|
||||||
-- Configuration for the floating action confirmation window
|
-- Configuration for the floating action confirmation window
|
||||||
confirmation = {
|
confirmation = {
|
||||||
|
|
@ -175,7 +191,7 @@ CONFIG *oil-confi
|
||||||
min_height = { 5, 0.1 },
|
min_height = { 5, 0.1 },
|
||||||
-- optionally define an integer/float for the exact height of the preview window
|
-- optionally define an integer/float for the exact height of the preview window
|
||||||
height = nil,
|
height = nil,
|
||||||
border = "rounded",
|
border = nil,
|
||||||
win_options = {
|
win_options = {
|
||||||
winblend = 0,
|
winblend = 0,
|
||||||
},
|
},
|
||||||
|
|
@ -188,7 +204,7 @@ CONFIG *oil-confi
|
||||||
max_height = { 10, 0.9 },
|
max_height = { 10, 0.9 },
|
||||||
min_height = { 5, 0.1 },
|
min_height = { 5, 0.1 },
|
||||||
height = nil,
|
height = nil,
|
||||||
border = "rounded",
|
border = nil,
|
||||||
minimized_border = "none",
|
minimized_border = "none",
|
||||||
win_options = {
|
win_options = {
|
||||||
winblend = 0,
|
winblend = 0,
|
||||||
|
|
@ -196,11 +212,11 @@ CONFIG *oil-confi
|
||||||
},
|
},
|
||||||
-- Configuration for the floating SSH window
|
-- Configuration for the floating SSH window
|
||||||
ssh = {
|
ssh = {
|
||||||
border = "rounded",
|
border = nil,
|
||||||
},
|
},
|
||||||
-- Configuration for the floating keymaps help window
|
-- Configuration for the floating keymaps help window
|
||||||
keymaps_help = {
|
keymaps_help = {
|
||||||
border = "rounded",
|
border = nil,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
<
|
<
|
||||||
|
|
@ -290,30 +306,58 @@ get_current_dir({bufnr}): nil|string *oil.get_current_di
|
||||||
Parameters:
|
Parameters:
|
||||||
{bufnr} `nil|integer`
|
{bufnr} `nil|integer`
|
||||||
|
|
||||||
open_float({dir}) *oil.open_float*
|
open_float({dir}, {opts}, {cb}) *oil.open_float*
|
||||||
Open oil browser in a floating window
|
Open oil browser in a floating window
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
{dir} `nil|string` When nil, open the parent of the current buffer, or the
|
{dir} `nil|string` When nil, open the parent of the current buffer, or
|
||||||
cwd if current buffer is not a file
|
the cwd if current buffer is not a file
|
||||||
|
{opts} `nil|oil.OpenOpts`
|
||||||
|
{preview} `nil|oil.OpenPreviewOpts` When present, open the preview
|
||||||
|
window after opening oil
|
||||||
|
{vertical} `nil|boolean` Open the buffer in a vertical split
|
||||||
|
{horizontal} `nil|boolean` Open the buffer in a horizontal split
|
||||||
|
{split} `nil|"aboveleft"|"belowright"|"topleft"|"botright"` S
|
||||||
|
plit modifier
|
||||||
|
{cb} `nil|fun()` Called after the oil buffer is ready
|
||||||
|
|
||||||
toggle_float({dir}) *oil.toggle_float*
|
toggle_float({dir}, {opts}, {cb}) *oil.toggle_float*
|
||||||
Open oil browser in a floating window, or close it if open
|
Open oil browser in a floating window, or close it if open
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
{dir} `nil|string` When nil, open the parent of the current buffer, or the
|
{dir} `nil|string` When nil, open the parent of the current buffer, or
|
||||||
cwd if current buffer is not a file
|
the cwd if current buffer is not a file
|
||||||
|
{opts} `nil|oil.OpenOpts`
|
||||||
|
{preview} `nil|oil.OpenPreviewOpts` When present, open the preview
|
||||||
|
window after opening oil
|
||||||
|
{vertical} `nil|boolean` Open the buffer in a vertical split
|
||||||
|
{horizontal} `nil|boolean` Open the buffer in a horizontal split
|
||||||
|
{split} `nil|"aboveleft"|"belowright"|"topleft"|"botright"` S
|
||||||
|
plit modifier
|
||||||
|
{cb} `nil|fun()` Called after the oil buffer is ready
|
||||||
|
|
||||||
open({dir}) *oil.open*
|
open({dir}, {opts}, {cb}) *oil.open*
|
||||||
Open oil browser for a directory
|
Open oil browser for a directory
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
{dir} `nil|string` When nil, open the parent of the current buffer, or the
|
{dir} `nil|string` When nil, open the parent of the current buffer, or
|
||||||
cwd if current buffer is not a file
|
the cwd if current buffer is not a file
|
||||||
|
{opts} `nil|oil.OpenOpts`
|
||||||
|
{preview} `nil|oil.OpenPreviewOpts` When present, open the preview
|
||||||
|
window after opening oil
|
||||||
|
{vertical} `nil|boolean` Open the buffer in a vertical split
|
||||||
|
{horizontal} `nil|boolean` Open the buffer in a horizontal split
|
||||||
|
{split} `nil|"aboveleft"|"belowright"|"topleft"|"botright"` S
|
||||||
|
plit modifier
|
||||||
|
{cb} `nil|fun()` Called after the oil buffer is ready
|
||||||
|
|
||||||
close() *oil.close*
|
close({opts}) *oil.close*
|
||||||
Restore the buffer that was present when oil was opened
|
Restore the buffer that was present when oil was opened
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
{opts} `nil|oil.CloseOpts`
|
||||||
|
{exit_if_last_buf} `nil|boolean` Exit vim if this oil buffer is the
|
||||||
|
last open buffer
|
||||||
|
|
||||||
open_preview({opts}, {callback}) *oil.open_preview*
|
open_preview({opts}, {callback}) *oil.open_preview*
|
||||||
Preview the entry under the cursor in a split
|
Preview the entry under the cursor in a split
|
||||||
|
|
@ -339,6 +383,10 @@ select({opts}, {callback}) *oil.selec
|
||||||
{tab} `nil|boolean` Open the buffer in a new tab
|
{tab} `nil|boolean` Open the buffer in a new tab
|
||||||
{close} `nil|boolean` Close the original oil buffer once
|
{close} `nil|boolean` Close the original oil buffer once
|
||||||
selection is made
|
selection is made
|
||||||
|
{handle_buffer_callback} `nil|fun(buf_id: integer)` If defined, all
|
||||||
|
other buffer related options here would be ignored. This
|
||||||
|
callback allows you to take over the process of opening
|
||||||
|
the buffer yourself.
|
||||||
{callback} `nil|fun(err: nil|string)` Called once all entries have been
|
{callback} `nil|fun(err: nil|string)` Called once all entries have been
|
||||||
opened
|
opened
|
||||||
|
|
||||||
|
|
@ -374,6 +422,7 @@ type *column-typ
|
||||||
Parameters:
|
Parameters:
|
||||||
{highlight} `string|fun(value: string): string` Highlight group, or
|
{highlight} `string|fun(value: string): string` Highlight group, or
|
||||||
function that returns a highlight group
|
function that returns a highlight group
|
||||||
|
{align} `"left"|"center"|"right"` Text alignment within the column
|
||||||
{icons} `table<string, string>` Mapping of entry type to icon
|
{icons} `table<string, string>` Mapping of entry type to icon
|
||||||
|
|
||||||
icon *column-icon*
|
icon *column-icon*
|
||||||
|
|
@ -383,6 +432,7 @@ icon *column-ico
|
||||||
Parameters:
|
Parameters:
|
||||||
{highlight} `string|fun(value: string): string` Highlight group, or
|
{highlight} `string|fun(value: string): string` Highlight group, or
|
||||||
function that returns a highlight group
|
function that returns a highlight group
|
||||||
|
{align} `"left"|"center"|"right"` Text alignment within the column
|
||||||
{default_file} `string` Fallback icon for files when nvim-web-devicons
|
{default_file} `string` Fallback icon for files when nvim-web-devicons
|
||||||
returns nil
|
returns nil
|
||||||
{directory} `string` Icon for directories
|
{directory} `string` Icon for directories
|
||||||
|
|
@ -390,13 +440,14 @@ icon *column-ico
|
||||||
the icon
|
the icon
|
||||||
|
|
||||||
size *column-size*
|
size *column-size*
|
||||||
Adapters: files, ssh
|
Adapters: files, ssh, s3
|
||||||
Sortable: this column can be used in view_props.sort
|
Sortable: this column can be used in view_props.sort
|
||||||
The size of the file
|
The size of the file
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
{highlight} `string|fun(value: string): string` Highlight group, or
|
{highlight} `string|fun(value: string): string` Highlight group, or
|
||||||
function that returns a highlight group
|
function that returns a highlight group
|
||||||
|
{align} `"left"|"center"|"right"` Text alignment within the column
|
||||||
|
|
||||||
permissions *column-permissions*
|
permissions *column-permissions*
|
||||||
Adapters: files, ssh
|
Adapters: files, ssh
|
||||||
|
|
@ -406,6 +457,7 @@ permissions *column-permission
|
||||||
Parameters:
|
Parameters:
|
||||||
{highlight} `string|fun(value: string): string` Highlight group, or
|
{highlight} `string|fun(value: string): string` Highlight group, or
|
||||||
function that returns a highlight group
|
function that returns a highlight group
|
||||||
|
{align} `"left"|"center"|"right"` Text alignment within the column
|
||||||
|
|
||||||
ctime *column-ctime*
|
ctime *column-ctime*
|
||||||
Adapters: files
|
Adapters: files
|
||||||
|
|
@ -415,6 +467,7 @@ ctime *column-ctim
|
||||||
Parameters:
|
Parameters:
|
||||||
{highlight} `string|fun(value: string): string` Highlight group, or
|
{highlight} `string|fun(value: string): string` Highlight group, or
|
||||||
function that returns a highlight group
|
function that returns a highlight group
|
||||||
|
{align} `"left"|"center"|"right"` Text alignment within the column
|
||||||
{format} `string` Format string (see :help strftime)
|
{format} `string` Format string (see :help strftime)
|
||||||
|
|
||||||
mtime *column-mtime*
|
mtime *column-mtime*
|
||||||
|
|
@ -425,6 +478,7 @@ mtime *column-mtim
|
||||||
Parameters:
|
Parameters:
|
||||||
{highlight} `string|fun(value: string): string` Highlight group, or
|
{highlight} `string|fun(value: string): string` Highlight group, or
|
||||||
function that returns a highlight group
|
function that returns a highlight group
|
||||||
|
{align} `"left"|"center"|"right"` Text alignment within the column
|
||||||
{format} `string` Format string (see :help strftime)
|
{format} `string` Format string (see :help strftime)
|
||||||
|
|
||||||
atime *column-atime*
|
atime *column-atime*
|
||||||
|
|
@ -435,16 +489,18 @@ atime *column-atim
|
||||||
Parameters:
|
Parameters:
|
||||||
{highlight} `string|fun(value: string): string` Highlight group, or
|
{highlight} `string|fun(value: string): string` Highlight group, or
|
||||||
function that returns a highlight group
|
function that returns a highlight group
|
||||||
|
{align} `"left"|"center"|"right"` Text alignment within the column
|
||||||
{format} `string` Format string (see :help strftime)
|
{format} `string` Format string (see :help strftime)
|
||||||
|
|
||||||
birthtime *column-birthtime*
|
birthtime *column-birthtime*
|
||||||
Adapters: files
|
Adapters: files, s3
|
||||||
Sortable: this column can be used in view_props.sort
|
Sortable: this column can be used in view_props.sort
|
||||||
The time the file was created
|
The time the file was created
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
{highlight} `string|fun(value: string): string` Highlight group, or
|
{highlight} `string|fun(value: string): string` Highlight group, or
|
||||||
function that returns a highlight group
|
function that returns a highlight group
|
||||||
|
{align} `"left"|"center"|"right"` Text alignment within the column
|
||||||
{format} `string` Format string (see :help strftime)
|
{format} `string` Format string (see :help strftime)
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
@ -508,6 +564,12 @@ change_sort *actions.change_sor
|
||||||
close *actions.close*
|
close *actions.close*
|
||||||
Close oil and restore original buffer
|
Close oil and restore original buffer
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
{exit_if_last_buf} `boolean` Exit vim if oil is closed as the last buffer
|
||||||
|
|
||||||
|
copy_to_system_clipboard *actions.copy_to_system_clipboard*
|
||||||
|
Copy the entry under the cursor to the system clipboard
|
||||||
|
|
||||||
open_cmdline *actions.open_cmdline*
|
open_cmdline *actions.open_cmdline*
|
||||||
Open vim cmdline with current entry as an argument
|
Open vim cmdline with current entry as an argument
|
||||||
|
|
||||||
|
|
@ -528,6 +590,12 @@ open_terminal *actions.open_termina
|
||||||
parent *actions.parent*
|
parent *actions.parent*
|
||||||
Navigate to the parent path
|
Navigate to the parent path
|
||||||
|
|
||||||
|
paste_from_system_clipboard *actions.paste_from_system_clipboard*
|
||||||
|
Paste the system clipboard into the current oil directory
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
{delete_original} `boolean` Delete the original file after copying
|
||||||
|
|
||||||
preview *actions.preview*
|
preview *actions.preview*
|
||||||
Open the entry under the cursor in a preview window, or close the preview
|
Open the entry under the cursor in a preview window, or close the preview
|
||||||
window if already open
|
window if already open
|
||||||
|
|
@ -541,6 +609,12 @@ preview *actions.previe
|
||||||
preview_scroll_down *actions.preview_scroll_down*
|
preview_scroll_down *actions.preview_scroll_down*
|
||||||
Scroll down in the preview window
|
Scroll down in the preview window
|
||||||
|
|
||||||
|
preview_scroll_left *actions.preview_scroll_left*
|
||||||
|
Scroll left in the preview window
|
||||||
|
|
||||||
|
preview_scroll_right *actions.preview_scroll_right*
|
||||||
|
Scroll right in the preview window
|
||||||
|
|
||||||
preview_scroll_up *actions.preview_scroll_up*
|
preview_scroll_up *actions.preview_scroll_up*
|
||||||
Scroll up in the preview window
|
Scroll up in the preview window
|
||||||
|
|
||||||
|
|
@ -570,6 +644,9 @@ send_to_qflist *actions.send_to_qflis
|
||||||
Parameters:
|
Parameters:
|
||||||
{action} `"r"|"a"` Replace or add to current quickfix list (see
|
{action} `"r"|"a"` Replace or add to current quickfix list (see
|
||||||
|setqflist-action|)
|
|setqflist-action|)
|
||||||
|
{only_matching_search} `boolean` Whether to only add the files that
|
||||||
|
matches the last search. This option only applies when search
|
||||||
|
highlighting is active
|
||||||
{target} `"qflist"|"loclist"` The target list to send files to
|
{target} `"qflist"|"loclist"` The target list to send files to
|
||||||
|
|
||||||
show_help *actions.show_help*
|
show_help *actions.show_help*
|
||||||
|
|
@ -591,24 +668,57 @@ yank_entry *actions.yank_entr
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
HIGHLIGHTS *oil-highlights*
|
HIGHLIGHTS *oil-highlights*
|
||||||
|
|
||||||
|
OilEmpty *hl-OilEmpty*
|
||||||
|
Empty column values
|
||||||
|
|
||||||
|
OilHidden *hl-OilHidden*
|
||||||
|
Hidden entry in an oil buffer
|
||||||
|
|
||||||
OilDir *hl-OilDir*
|
OilDir *hl-OilDir*
|
||||||
Directory names in an oil buffer
|
Directory names in an oil buffer
|
||||||
|
|
||||||
|
OilDirHidden *hl-OilDirHidden*
|
||||||
|
Hidden directory names in an oil buffer
|
||||||
|
|
||||||
OilDirIcon *hl-OilDirIcon*
|
OilDirIcon *hl-OilDirIcon*
|
||||||
Icon for directories
|
Icon for directories
|
||||||
|
|
||||||
OilSocket *hl-OilSocket*
|
OilSocket *hl-OilSocket*
|
||||||
Socket files in an oil buffer
|
Socket files in an oil buffer
|
||||||
|
|
||||||
|
OilSocketHidden *hl-OilSocketHidden*
|
||||||
|
Hidden socket files in an oil buffer
|
||||||
|
|
||||||
OilLink *hl-OilLink*
|
OilLink *hl-OilLink*
|
||||||
Soft links in an oil buffer
|
Soft links in an oil buffer
|
||||||
|
|
||||||
|
OilOrphanLink *hl-OilOrphanLink*
|
||||||
|
Orphaned soft links in an oil buffer
|
||||||
|
|
||||||
|
OilLinkHidden *hl-OilLinkHidden*
|
||||||
|
Hidden soft links in an oil buffer
|
||||||
|
|
||||||
|
OilOrphanLinkHidden *hl-OilOrphanLinkHidden*
|
||||||
|
Hidden orphaned soft links in an oil buffer
|
||||||
|
|
||||||
OilLinkTarget *hl-OilLinkTarget*
|
OilLinkTarget *hl-OilLinkTarget*
|
||||||
The target of a soft link
|
The target of a soft link
|
||||||
|
|
||||||
|
OilOrphanLinkTarget *hl-OilOrphanLinkTarget*
|
||||||
|
The target of an orphaned soft link
|
||||||
|
|
||||||
|
OilLinkTargetHidden *hl-OilLinkTargetHidden*
|
||||||
|
The target of a hidden soft link
|
||||||
|
|
||||||
|
OilOrphanLinkTargetHidden *hl-OilOrphanLinkTargetHidden*
|
||||||
|
The target of an hidden orphaned soft link
|
||||||
|
|
||||||
OilFile *hl-OilFile*
|
OilFile *hl-OilFile*
|
||||||
Normal files in an oil buffer
|
Normal files in an oil buffer
|
||||||
|
|
||||||
|
OilFileHidden *hl-OilFileHidden*
|
||||||
|
Hidden normal files in an oil buffer
|
||||||
|
|
||||||
OilCreate *hl-OilCreate*
|
OilCreate *hl-OilCreate*
|
||||||
Create action in the oil preview window
|
Create action in the oil preview window
|
||||||
|
|
||||||
|
|
@ -647,9 +757,9 @@ of being permanently deleted. You can browse the trash for a directory using
|
||||||
the `toggle_trash` action (bound to `g\` by default). You can view all files
|
the `toggle_trash` action (bound to `g\` by default). You can view all files
|
||||||
in the trash with `:Oil --trash /`.
|
in the trash with `:Oil --trash /`.
|
||||||
|
|
||||||
To restore files, simply delete them from the trash and put them in the desired
|
To restore files, simply move them from the trash to the desired destination,
|
||||||
destination, the same as any other file operation. If you delete files from the
|
the same as any other file operation. If you delete files from the trash they
|
||||||
trash they will be permanently deleted (purged).
|
will be permanently deleted (purged).
|
||||||
|
|
||||||
Linux:
|
Linux:
|
||||||
Oil supports the FreeDesktop trash specification.
|
Oil supports the FreeDesktop trash specification.
|
||||||
|
|
@ -662,7 +772,7 @@ Mac:
|
||||||
(instead of being able to see files that were trashed from a directory).
|
(instead of being able to see files that were trashed from a directory).
|
||||||
|
|
||||||
Windows:
|
Windows:
|
||||||
Oil does not yet support the Windows trash. PRs are welcome!
|
Oil supports the Windows Recycle Bin. All features should work.
|
||||||
|
|
||||||
================================================================================
|
================================================================================
|
||||||
vim:tw=80:ts=2:ft=help:norl:syntax=help:
|
vim:tw=80:ts=2:ft=help:norl:syntax=help:
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,8 @@ require("oil").setup({
|
||||||
```lua
|
```lua
|
||||||
-- Declare a global function to retrieve the current directory
|
-- Declare a global function to retrieve the current directory
|
||||||
function _G.get_oil_winbar()
|
function _G.get_oil_winbar()
|
||||||
local dir = require("oil").get_current_dir()
|
local bufnr = vim.api.nvim_win_get_buf(vim.g.statusline_winid)
|
||||||
|
local dir = require("oil").get_current_dir(bufnr)
|
||||||
if dir then
|
if dir then
|
||||||
return vim.fn.fnamemodify(dir, ":~")
|
return vim.fn.fnamemodify(dir, ":~")
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,30 @@ M.preview_scroll_up = {
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
M.preview_scroll_left = {
|
||||||
|
desc = "Scroll left in the preview window",
|
||||||
|
callback = function()
|
||||||
|
local winid = util.get_preview_win()
|
||||||
|
if winid then
|
||||||
|
vim.api.nvim_win_call(winid, function()
|
||||||
|
vim.cmd.normal({ "zH", bang = true })
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
M.preview_scroll_right = {
|
||||||
|
desc = "Scroll right in the preview window",
|
||||||
|
callback = function()
|
||||||
|
local winid = util.get_preview_win()
|
||||||
|
if winid then
|
||||||
|
vim.api.nvim_win_call(winid, function()
|
||||||
|
vim.cmd.normal({ "zL", bang = true })
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
M.parent = {
|
M.parent = {
|
||||||
desc = "Navigate to the parent path",
|
desc = "Navigate to the parent path",
|
||||||
callback = oil.open,
|
callback = oil.open,
|
||||||
|
|
@ -143,7 +167,16 @@ M.parent = {
|
||||||
|
|
||||||
M.close = {
|
M.close = {
|
||||||
desc = "Close oil and restore original buffer",
|
desc = "Close oil and restore original buffer",
|
||||||
callback = oil.close,
|
callback = function(opts)
|
||||||
|
opts = opts or {}
|
||||||
|
oil.close(opts)
|
||||||
|
end,
|
||||||
|
parameters = {
|
||||||
|
exit_if_last_buf = {
|
||||||
|
type = "boolean",
|
||||||
|
desc = "Exit vim if oil is closed as the last buffer",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
---@param cmd string
|
---@param cmd string
|
||||||
|
|
@ -220,13 +253,24 @@ M.open_terminal = {
|
||||||
assert(dir, "Oil buffer with files adapter must have current directory")
|
assert(dir, "Oil buffer with files adapter must have current directory")
|
||||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||||
vim.api.nvim_set_current_buf(bufnr)
|
vim.api.nvim_set_current_buf(bufnr)
|
||||||
|
if vim.fn.has("nvim-0.11") == 1 then
|
||||||
|
vim.fn.jobstart(vim.o.shell, { cwd = dir, term = true })
|
||||||
|
else
|
||||||
|
---@diagnostic disable-next-line: deprecated
|
||||||
vim.fn.termopen(vim.o.shell, { cwd = dir })
|
vim.fn.termopen(vim.o.shell, { cwd = dir })
|
||||||
|
end
|
||||||
elseif adapter.name == "ssh" then
|
elseif adapter.name == "ssh" then
|
||||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||||
vim.api.nvim_set_current_buf(bufnr)
|
vim.api.nvim_set_current_buf(bufnr)
|
||||||
local url = require("oil.adapters.ssh").parse_url(bufname)
|
local url = require("oil.adapters.ssh").parse_url(bufname)
|
||||||
local cmd = require("oil.adapters.ssh.connection").create_ssh_command(url)
|
local cmd = require("oil.adapters.ssh.connection").create_ssh_command(url)
|
||||||
local term_id = vim.fn.termopen(cmd)
|
local term_id
|
||||||
|
if vim.fn.has("nvim-0.11") == 1 then
|
||||||
|
term_id = vim.fn.jobstart(cmd, { term = true })
|
||||||
|
else
|
||||||
|
---@diagnostic disable-next-line: deprecated
|
||||||
|
term_id = vim.fn.termopen(cmd)
|
||||||
|
end
|
||||||
if term_id then
|
if term_id then
|
||||||
vim.api.nvim_chan_send(term_id, string.format("cd %s\n", url.path))
|
vim.api.nvim_chan_send(term_id, string.format("cd %s\n", url.path))
|
||||||
end
|
end
|
||||||
|
|
@ -409,6 +453,26 @@ M.copy_entry_filename = {
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
M.copy_to_system_clipboard = {
|
||||||
|
desc = "Copy the entry under the cursor to the system clipboard",
|
||||||
|
callback = function()
|
||||||
|
require("oil.clipboard").copy_to_system_clipboard()
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
M.paste_from_system_clipboard = {
|
||||||
|
desc = "Paste the system clipboard into the current oil directory",
|
||||||
|
callback = function(opts)
|
||||||
|
require("oil.clipboard").paste_from_system_clipboard(opts and opts.delete_original)
|
||||||
|
end,
|
||||||
|
parameters = {
|
||||||
|
delete_original = {
|
||||||
|
type = "boolean",
|
||||||
|
desc = "Delete the original file after copying",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
M.open_cmdline_dir = {
|
M.open_cmdline_dir = {
|
||||||
desc = "Open vim cmdline with current directory as an argument",
|
desc = "Open vim cmdline with current directory as an argument",
|
||||||
deprecated = true,
|
deprecated = true,
|
||||||
|
|
@ -495,10 +559,12 @@ M.send_to_qflist = {
|
||||||
opts = vim.tbl_deep_extend("keep", opts or {}, {
|
opts = vim.tbl_deep_extend("keep", opts or {}, {
|
||||||
target = "qflist",
|
target = "qflist",
|
||||||
action = "r",
|
action = "r",
|
||||||
|
only_matching_search = false,
|
||||||
})
|
})
|
||||||
util.send_to_quickfix({
|
util.send_to_quickfix({
|
||||||
target = opts.target,
|
target = opts.target,
|
||||||
action = opts.action,
|
action = opts.action,
|
||||||
|
only_matching_search = opts.only_matching_search,
|
||||||
})
|
})
|
||||||
end,
|
end,
|
||||||
parameters = {
|
parameters = {
|
||||||
|
|
@ -510,6 +576,10 @@ M.send_to_qflist = {
|
||||||
type = '"r"|"a"',
|
type = '"r"|"a"',
|
||||||
desc = "Replace or add to current quickfix list (see |setqflist-action|)",
|
desc = "Replace or add to current quickfix list (see |setqflist-action|)",
|
||||||
},
|
},
|
||||||
|
only_matching_search = {
|
||||||
|
type = "boolean",
|
||||||
|
desc = "Whether to only add the files that matches the last search. This option only applies when search highlighting is active",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,15 @@ local config = require("oil.config")
|
||||||
local constants = require("oil.constants")
|
local constants = require("oil.constants")
|
||||||
local fs = require("oil.fs")
|
local fs = require("oil.fs")
|
||||||
local git = require("oil.git")
|
local git = require("oil.git")
|
||||||
|
local log = require("oil.log")
|
||||||
local permissions = require("oil.adapters.files.permissions")
|
local permissions = require("oil.adapters.files.permissions")
|
||||||
local trash = require("oil.adapters.files.trash")
|
|
||||||
local util = require("oil.util")
|
local util = require("oil.util")
|
||||||
local uv = vim.uv or vim.loop
|
local uv = vim.uv or vim.loop
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local FIELD_NAME = constants.FIELD_NAME
|
local FIELD_NAME = constants.FIELD_NAME
|
||||||
|
local FIELD_TYPE = constants.FIELD_TYPE
|
||||||
local FIELD_META = constants.FIELD_META
|
local FIELD_META = constants.FIELD_META
|
||||||
|
|
||||||
local function read_link_data(path, cb)
|
local function read_link_data(path, cb)
|
||||||
|
|
@ -50,21 +51,12 @@ end
|
||||||
|
|
||||||
local file_columns = {}
|
local file_columns = {}
|
||||||
|
|
||||||
local fs_stat_meta_fields = {
|
|
||||||
stat = function(parent_url, entry, cb)
|
|
||||||
local _, path = util.parse_url(parent_url)
|
|
||||||
assert(path)
|
|
||||||
local dir = fs.posix_to_os_path(path .. entry[FIELD_NAME])
|
|
||||||
uv.fs_stat(dir, cb)
|
|
||||||
end,
|
|
||||||
}
|
|
||||||
|
|
||||||
file_columns.size = {
|
file_columns.size = {
|
||||||
meta_fields = fs_stat_meta_fields,
|
require_stat = true,
|
||||||
|
|
||||||
render = function(entry, conf)
|
render = function(entry, conf)
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
local stat = meta.stat
|
local stat = meta and meta.stat
|
||||||
if not stat then
|
if not stat then
|
||||||
return columns.EMPTY
|
return columns.EMPTY
|
||||||
end
|
end
|
||||||
|
|
@ -81,7 +73,7 @@ file_columns.size = {
|
||||||
|
|
||||||
get_sort_value = function(entry)
|
get_sort_value = function(entry)
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
local stat = meta.stat
|
local stat = meta and meta.stat
|
||||||
if stat then
|
if stat then
|
||||||
return stat.size
|
return stat.size
|
||||||
else
|
else
|
||||||
|
|
@ -97,11 +89,11 @@ file_columns.size = {
|
||||||
-- TODO support file permissions on windows
|
-- TODO support file permissions on windows
|
||||||
if not fs.is_windows then
|
if not fs.is_windows then
|
||||||
file_columns.permissions = {
|
file_columns.permissions = {
|
||||||
meta_fields = fs_stat_meta_fields,
|
require_stat = true,
|
||||||
|
|
||||||
render = function(entry, conf)
|
render = function(entry, conf)
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
local stat = meta.stat
|
local stat = meta and meta.stat
|
||||||
if not stat then
|
if not stat then
|
||||||
return columns.EMPTY
|
return columns.EMPTY
|
||||||
end
|
end
|
||||||
|
|
@ -114,7 +106,7 @@ if not fs.is_windows then
|
||||||
|
|
||||||
compare = function(entry, parsed_value)
|
compare = function(entry, parsed_value)
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
if parsed_value and meta.stat and meta.stat.mode then
|
if parsed_value and meta and meta.stat and meta.stat.mode then
|
||||||
local mask = bit.lshift(1, 12) - 1
|
local mask = bit.lshift(1, 12) - 1
|
||||||
local old_mode = bit.band(meta.stat.mode, mask)
|
local old_mode = bit.band(meta.stat.mode, mask)
|
||||||
if parsed_value ~= old_mode then
|
if parsed_value ~= old_mode then
|
||||||
|
|
@ -160,11 +152,11 @@ end)
|
||||||
|
|
||||||
for _, time_key in ipairs({ "ctime", "mtime", "atime", "birthtime" }) do
|
for _, time_key in ipairs({ "ctime", "mtime", "atime", "birthtime" }) do
|
||||||
file_columns[time_key] = {
|
file_columns[time_key] = {
|
||||||
meta_fields = fs_stat_meta_fields,
|
require_stat = true,
|
||||||
|
|
||||||
render = function(entry, conf)
|
render = function(entry, conf)
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
local stat = meta.stat
|
local stat = meta and meta.stat
|
||||||
if not stat then
|
if not stat then
|
||||||
return columns.EMPTY
|
return columns.EMPTY
|
||||||
end
|
end
|
||||||
|
|
@ -187,7 +179,20 @@ for _, time_key in ipairs({ "ctime", "mtime", "atime", "birthtime" }) do
|
||||||
local fmt = conf and conf.format
|
local fmt = conf and conf.format
|
||||||
local pattern
|
local pattern
|
||||||
if fmt then
|
if fmt then
|
||||||
pattern = fmt:gsub("%%.", "%%S+")
|
-- Replace placeholders with a pattern that matches non-space characters (e.g. %H -> %S+)
|
||||||
|
-- and whitespace with a pattern that matches any amount of whitespace
|
||||||
|
-- e.g. "%b %d %Y" -> "%S+%s+%S+%s+%S+"
|
||||||
|
pattern = fmt
|
||||||
|
:gsub("%%.", "%%S+")
|
||||||
|
:gsub("%s+", "%%s+")
|
||||||
|
-- escape `()[]` because those are special characters in Lua patterns
|
||||||
|
:gsub(
|
||||||
|
"%(",
|
||||||
|
"%%("
|
||||||
|
)
|
||||||
|
:gsub("%)", "%%)")
|
||||||
|
:gsub("%[", "%%[")
|
||||||
|
:gsub("%]", "%%]")
|
||||||
else
|
else
|
||||||
pattern = "%S+%s+%d+%s+%d%d:?%d%d"
|
pattern = "%S+%s+%d+%s+%d%d:?%d%d"
|
||||||
end
|
end
|
||||||
|
|
@ -196,7 +201,7 @@ for _, time_key in ipairs({ "ctime", "mtime", "atime", "birthtime" }) do
|
||||||
|
|
||||||
get_sort_value = function(entry)
|
get_sort_value = function(entry)
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
local stat = meta.stat
|
local stat = meta and meta.stat
|
||||||
if stat then
|
if stat then
|
||||||
return stat[time_key].sec
|
return stat[time_key].sec
|
||||||
else
|
else
|
||||||
|
|
@ -206,6 +211,20 @@ for _, time_key in ipairs({ "ctime", "mtime", "atime", "birthtime" }) do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param column_defs table[]
|
||||||
|
---@return boolean
|
||||||
|
local function columns_require_stat(column_defs)
|
||||||
|
for _, def in ipairs(column_defs) do
|
||||||
|
local name = util.split_config(def)
|
||||||
|
local column = M.get_column(name)
|
||||||
|
---@diagnostic disable-next-line: undefined-field We only put this on the files adapter columns
|
||||||
|
if column and column.require_stat then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
---@param name string
|
---@param name string
|
||||||
---@return nil|oil.ColumnDefinition
|
---@return nil|oil.ColumnDefinition
|
||||||
M.get_column = function(name)
|
M.get_column = function(name)
|
||||||
|
|
@ -257,7 +276,7 @@ M.normalize_url = function(url, callback)
|
||||||
local norm_path = util.addslash(fs.os_to_posix_path(realpath))
|
local norm_path = util.addslash(fs.os_to_posix_path(realpath))
|
||||||
callback(scheme .. norm_path)
|
callback(scheme .. norm_path)
|
||||||
else
|
else
|
||||||
callback(realpath)
|
callback(vim.fn.fnamemodify(realpath, ":."))
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
)
|
)
|
||||||
|
|
@ -283,12 +302,102 @@ M.get_entry_path = function(url, entry, cb)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param parent_dir string
|
||||||
|
---@param entry oil.InternalEntry
|
||||||
|
---@param require_stat boolean
|
||||||
|
---@param cb fun(err?: string)
|
||||||
|
local function fetch_entry_metadata(parent_dir, entry, require_stat, cb)
|
||||||
|
local entry_path = fs.posix_to_os_path(parent_dir .. entry[FIELD_NAME])
|
||||||
|
local meta = entry[FIELD_META]
|
||||||
|
if not meta then
|
||||||
|
meta = {}
|
||||||
|
entry[FIELD_META] = meta
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Sometimes fs_readdir entries don't have a type, so we need to stat them.
|
||||||
|
-- See https://github.com/stevearc/oil.nvim/issues/543
|
||||||
|
if not require_stat and not entry[FIELD_TYPE] then
|
||||||
|
require_stat = true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Make sure we always get fs_stat info for links
|
||||||
|
if entry[FIELD_TYPE] == "link" then
|
||||||
|
read_link_data(entry_path, function(link_err, link, link_stat)
|
||||||
|
if link_err then
|
||||||
|
log.warn("Error reading link data %s: %s", entry_path, link_err)
|
||||||
|
return cb()
|
||||||
|
end
|
||||||
|
meta.link = link
|
||||||
|
if link_stat then
|
||||||
|
-- Use the fstat of the linked file as the stat for the link
|
||||||
|
meta.link_stat = link_stat
|
||||||
|
meta.stat = link_stat
|
||||||
|
elseif require_stat then
|
||||||
|
-- The link is broken, so let's use the stat of the link itself
|
||||||
|
uv.fs_lstat(entry_path, function(stat_err, stat)
|
||||||
|
if stat_err then
|
||||||
|
log.warn("Error lstat link file %s: %s", entry_path, stat_err)
|
||||||
|
return cb()
|
||||||
|
end
|
||||||
|
meta.stat = stat
|
||||||
|
cb()
|
||||||
|
end)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
cb()
|
||||||
|
end)
|
||||||
|
elseif require_stat then
|
||||||
|
uv.fs_stat(entry_path, function(stat_err, stat)
|
||||||
|
if stat_err then
|
||||||
|
log.warn("Error stat file %s: %s", entry_path, stat_err)
|
||||||
|
return cb()
|
||||||
|
end
|
||||||
|
assert(stat)
|
||||||
|
entry[FIELD_TYPE] = stat.type
|
||||||
|
meta.stat = stat
|
||||||
|
cb()
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
cb()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- On windows, sometimes the entry type from fs_readdir is "link" but the actual type is not.
|
||||||
|
-- See https://github.com/stevearc/oil.nvim/issues/535
|
||||||
|
if fs.is_windows then
|
||||||
|
local old_fetch_metadata = fetch_entry_metadata
|
||||||
|
fetch_entry_metadata = function(parent_dir, entry, require_stat, cb)
|
||||||
|
if entry[FIELD_TYPE] == "link" then
|
||||||
|
local entry_path = fs.posix_to_os_path(parent_dir .. entry[FIELD_NAME])
|
||||||
|
uv.fs_lstat(entry_path, function(stat_err, stat)
|
||||||
|
if stat_err then
|
||||||
|
log.warn("Error lstat link file %s: %s", entry_path, stat_err)
|
||||||
|
return old_fetch_metadata(parent_dir, entry, require_stat, cb)
|
||||||
|
end
|
||||||
|
assert(stat)
|
||||||
|
entry[FIELD_TYPE] = stat.type
|
||||||
|
local meta = entry[FIELD_META]
|
||||||
|
if not meta then
|
||||||
|
meta = {}
|
||||||
|
entry[FIELD_META] = meta
|
||||||
|
end
|
||||||
|
meta.stat = stat
|
||||||
|
old_fetch_metadata(parent_dir, entry, require_stat, cb)
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
return old_fetch_metadata(parent_dir, entry, require_stat, cb)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
---@param url string
|
---@param url string
|
||||||
---@param column_defs string[]
|
---@param column_defs string[]
|
||||||
---@param cb fun(err?: string, entries?: oil.InternalEntry[], fetch_more?: fun())
|
---@param cb fun(err?: string, entries?: oil.InternalEntry[], fetch_more?: fun())
|
||||||
local function list_windows_drives(url, column_defs, cb)
|
local function list_windows_drives(url, column_defs, cb)
|
||||||
---@cast M oil.FilesAdapter
|
local _, path = util.parse_url(url)
|
||||||
local fetch_meta = columns.get_metadata_fetcher(M, column_defs)
|
assert(path)
|
||||||
|
local require_stat = columns_require_stat(column_defs)
|
||||||
local stdout = ""
|
local stdout = ""
|
||||||
local jid = vim.fn.jobstart({ "wmic", "logicaldisk", "get", "name" }, {
|
local jid = vim.fn.jobstart({ "wmic", "logicaldisk", "get", "name" }, {
|
||||||
stdout_buffered = true,
|
stdout_buffered = true,
|
||||||
|
|
@ -318,14 +427,8 @@ local function list_windows_drives(url, column_defs, cb)
|
||||||
else
|
else
|
||||||
disk = disk:gsub(":%s*$", "")
|
disk = disk:gsub(":%s*$", "")
|
||||||
local cache_entry = cache.create_entry(url, disk, "directory")
|
local cache_entry = cache.create_entry(url, disk, "directory")
|
||||||
fetch_meta(url, cache_entry, function(err)
|
|
||||||
if err then
|
|
||||||
complete_disk_cb(err)
|
|
||||||
else
|
|
||||||
table.insert(internal_entries, cache_entry)
|
table.insert(internal_entries, cache_entry)
|
||||||
complete_disk_cb()
|
fetch_entry_metadata(path, cache_entry, require_stat, complete_disk_cb)
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
@ -345,8 +448,7 @@ M.list = function(url, column_defs, cb)
|
||||||
return list_windows_drives(url, column_defs, cb)
|
return list_windows_drives(url, column_defs, cb)
|
||||||
end
|
end
|
||||||
local dir = fs.posix_to_os_path(path)
|
local dir = fs.posix_to_os_path(path)
|
||||||
---@cast M oil.Adapter
|
local require_stat = columns_require_stat(column_defs)
|
||||||
local fetch_meta = columns.get_metadata_fetcher(M, column_defs)
|
|
||||||
|
|
||||||
---@diagnostic disable-next-line: param-type-mismatch, discard-returns
|
---@diagnostic disable-next-line: param-type-mismatch, discard-returns
|
||||||
uv.fs_opendir(dir, function(open_err, fd)
|
uv.fs_opendir(dir, function(open_err, fd)
|
||||||
|
|
@ -378,28 +480,8 @@ M.list = function(url, column_defs, cb)
|
||||||
end)
|
end)
|
||||||
for _, entry in ipairs(entries) do
|
for _, entry in ipairs(entries) do
|
||||||
local cache_entry = cache.create_entry(url, entry.name, entry.type)
|
local cache_entry = cache.create_entry(url, entry.name, entry.type)
|
||||||
fetch_meta(url, cache_entry, function(meta_err)
|
|
||||||
table.insert(internal_entries, cache_entry)
|
table.insert(internal_entries, cache_entry)
|
||||||
local meta = cache_entry[FIELD_META]
|
fetch_entry_metadata(path, cache_entry, require_stat, poll)
|
||||||
-- Make sure we always get fs_stat info for links
|
|
||||||
if entry.type == "link" then
|
|
||||||
read_link_data(fs.join(dir, entry.name), function(link_err, link, link_stat)
|
|
||||||
if link_err then
|
|
||||||
poll(link_err)
|
|
||||||
else
|
|
||||||
if not meta then
|
|
||||||
meta = {}
|
|
||||||
cache_entry[FIELD_META] = meta
|
|
||||||
end
|
|
||||||
meta.link = link
|
|
||||||
meta.link_stat = link_stat
|
|
||||||
poll()
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
else
|
|
||||||
poll()
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
uv.fs_closedir(fd, function(close_err)
|
uv.fs_closedir(fd, function(close_err)
|
||||||
|
|
@ -417,26 +499,6 @@ M.list = function(url, column_defs, cb)
|
||||||
end, 10000)
|
end, 10000)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@type nil|integer[]
|
|
||||||
local _group_ids
|
|
||||||
---@return integer[]
|
|
||||||
local function get_group_ids()
|
|
||||||
if not _group_ids then
|
|
||||||
local output = vim.fn.system({ "id", "-G" })
|
|
||||||
if vim.v.shell_error == 0 then
|
|
||||||
_group_ids = vim.tbl_map(tonumber, vim.split(output, "%s+", { trimempty = true }))
|
|
||||||
else
|
|
||||||
-- If the id command fails, fall back to just using the process group
|
|
||||||
_group_ids = { uv.getgid() }
|
|
||||||
vim.notify(
|
|
||||||
"[oil] missing the `id` command. Some directories may not be modifiable even if you have group access.",
|
|
||||||
vim.log.levels.WARN
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return _group_ids
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
---@return boolean
|
---@return boolean
|
||||||
M.is_modifiable = function(bufnr)
|
M.is_modifiable = function(bufnr)
|
||||||
|
|
@ -452,20 +514,8 @@ M.is_modifiable = function(bufnr)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Can't do permissions checks on windows
|
-- fs_access can return nil, force boolean return
|
||||||
if fs.is_windows then
|
return uv.fs_access(dir, "W") == true
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local uid = uv.getuid()
|
|
||||||
local rwx = stat.mode
|
|
||||||
if uid == stat.uid then
|
|
||||||
rwx = bit.bor(rwx, bit.rshift(stat.mode, 6))
|
|
||||||
end
|
|
||||||
if vim.tbl_contains(get_group_ids(), stat.gid) then
|
|
||||||
rwx = bit.bor(rwx, bit.rshift(stat.mode, 3))
|
|
||||||
end
|
|
||||||
return bit.band(rwx, 2) ~= 0
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param action oil.Action
|
---@param action oil.Action
|
||||||
|
|
@ -489,7 +539,7 @@ M.render_action = function(action)
|
||||||
return string.format("DELETE %s", short_path)
|
return string.format("DELETE %s", short_path)
|
||||||
end
|
end
|
||||||
elseif action.type == "move" or action.type == "copy" then
|
elseif action.type == "move" or action.type == "copy" then
|
||||||
local dest_adapter = config.get_adapter_by_scheme(action.dest_url)
|
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
||||||
if dest_adapter == M then
|
if dest_adapter == M then
|
||||||
local _, src_path = util.parse_url(action.src_url)
|
local _, src_path = util.parse_url(action.src_url)
|
||||||
assert(src_path)
|
assert(src_path)
|
||||||
|
|
@ -569,20 +619,12 @@ M.perform_action = function(action, cb)
|
||||||
end
|
end
|
||||||
|
|
||||||
if config.delete_to_trash then
|
if config.delete_to_trash then
|
||||||
if config.trash_command then
|
|
||||||
vim.notify_once(
|
|
||||||
"Oil now has native support for trash. Remove the `trash_command` from your config to try it out!",
|
|
||||||
vim.log.levels.WARN
|
|
||||||
)
|
|
||||||
trash.recursive_delete(path, cb)
|
|
||||||
else
|
|
||||||
require("oil.adapters.trash").delete_to_trash(path, cb)
|
require("oil.adapters.trash").delete_to_trash(path, cb)
|
||||||
end
|
|
||||||
else
|
else
|
||||||
fs.recursive_delete(action.entry_type, path, cb)
|
fs.recursive_delete(action.entry_type, path, cb)
|
||||||
end
|
end
|
||||||
elseif action.type == "move" then
|
elseif action.type == "move" then
|
||||||
local dest_adapter = config.get_adapter_by_scheme(action.dest_url)
|
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
||||||
if dest_adapter == M then
|
if dest_adapter == M then
|
||||||
local _, src_path = util.parse_url(action.src_url)
|
local _, src_path = util.parse_url(action.src_url)
|
||||||
assert(src_path)
|
assert(src_path)
|
||||||
|
|
@ -600,7 +642,7 @@ M.perform_action = function(action, cb)
|
||||||
cb("files adapter doesn't support cross-adapter move")
|
cb("files adapter doesn't support cross-adapter move")
|
||||||
end
|
end
|
||||||
elseif action.type == "copy" then
|
elseif action.type == "copy" then
|
||||||
local dest_adapter = config.get_adapter_by_scheme(action.dest_url)
|
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
||||||
if dest_adapter == M then
|
if dest_adapter == M then
|
||||||
local _, src_path = util.parse_url(action.src_url)
|
local _, src_path = util.parse_url(action.src_url)
|
||||||
assert(src_path)
|
assert(src_path)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
---@param exe_modifier nil|false|string
|
---@param exe_modifier false|string
|
||||||
---@param num integer
|
---@param num integer
|
||||||
---@return string
|
---@return string
|
||||||
local function perm_to_str(exe_modifier, num)
|
local function perm_to_str(exe_modifier, num)
|
||||||
|
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
local config = require("oil.config")
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
M.recursive_delete = function(path, cb)
|
|
||||||
local stdout = {}
|
|
||||||
local stderr = {}
|
|
||||||
local cmd
|
|
||||||
if config.trash_command:find("%s") then
|
|
||||||
cmd = string.format("%s %s", config.trash_command, vim.fn.shellescape(path))
|
|
||||||
else
|
|
||||||
cmd = { config.trash_command, path }
|
|
||||||
end
|
|
||||||
local jid = vim.fn.jobstart(cmd, {
|
|
||||||
stdout_buffered = true,
|
|
||||||
stderr_buffered = true,
|
|
||||||
on_stdout = function(j, output)
|
|
||||||
stdout = output
|
|
||||||
end,
|
|
||||||
on_stderr = function(j, output)
|
|
||||||
stderr = output
|
|
||||||
end,
|
|
||||||
on_exit = function(j, exit_code)
|
|
||||||
if exit_code == 0 then
|
|
||||||
cb()
|
|
||||||
else
|
|
||||||
cb(
|
|
||||||
string.format(
|
|
||||||
"Error moving '%s' to trash:\n stdout: %s\n stderr: %s",
|
|
||||||
path,
|
|
||||||
table.concat(stdout, "\n "),
|
|
||||||
table.concat(stderr, "\n ")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
if jid == 0 then
|
|
||||||
cb(string.format("Passed invalid argument '%s' to '%s'", path, config.trash_command))
|
|
||||||
elseif jid == -1 then
|
|
||||||
cb(string.format("'%s' is not executable", config.trash_command))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
389
lua/oil/adapters/s3.lua
Normal file
389
lua/oil/adapters/s3.lua
Normal file
|
|
@ -0,0 +1,389 @@
|
||||||
|
local config = require("oil.config")
|
||||||
|
local constants = require("oil.constants")
|
||||||
|
local files = require("oil.adapters.files")
|
||||||
|
local fs = require("oil.fs")
|
||||||
|
local loading = require("oil.loading")
|
||||||
|
local pathutil = require("oil.pathutil")
|
||||||
|
local s3fs = require("oil.adapters.s3.s3fs")
|
||||||
|
local util = require("oil.util")
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local FIELD_META = constants.FIELD_META
|
||||||
|
|
||||||
|
---@class (exact) oil.s3Url
|
||||||
|
---@field scheme string
|
||||||
|
---@field bucket nil|string
|
||||||
|
---@field path nil|string
|
||||||
|
|
||||||
|
---@param oil_url string
|
||||||
|
---@return oil.s3Url
|
||||||
|
M.parse_url = function(oil_url)
|
||||||
|
local scheme, url = util.parse_url(oil_url)
|
||||||
|
assert(scheme and url, string.format("Malformed input url '%s'", oil_url))
|
||||||
|
local ret = { scheme = scheme }
|
||||||
|
local bucket, path = url:match("^([^/]+)/?(.*)$")
|
||||||
|
ret.bucket = bucket
|
||||||
|
ret.path = path ~= "" and path or nil
|
||||||
|
if not ret.bucket and ret.path then
|
||||||
|
error(string.format("Parsing error for s3 url: %s", oil_url))
|
||||||
|
end
|
||||||
|
---@cast ret oil.s3Url
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param url oil.s3Url
|
||||||
|
---@return string
|
||||||
|
local function url_to_str(url)
|
||||||
|
local pieces = { url.scheme }
|
||||||
|
if url.bucket then
|
||||||
|
assert(url.bucket ~= "")
|
||||||
|
table.insert(pieces, url.bucket)
|
||||||
|
table.insert(pieces, "/")
|
||||||
|
end
|
||||||
|
if url.path then
|
||||||
|
assert(url.path ~= "")
|
||||||
|
table.insert(pieces, url.path)
|
||||||
|
end
|
||||||
|
return table.concat(pieces, "")
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param url oil.s3Url
|
||||||
|
---@param is_folder boolean
|
||||||
|
---@return string
|
||||||
|
local function url_to_s3(url, is_folder)
|
||||||
|
local pieces = { "s3://" }
|
||||||
|
if url.bucket then
|
||||||
|
assert(url.bucket ~= "")
|
||||||
|
table.insert(pieces, url.bucket)
|
||||||
|
table.insert(pieces, "/")
|
||||||
|
end
|
||||||
|
if url.path then
|
||||||
|
assert(url.path ~= "")
|
||||||
|
table.insert(pieces, url.path)
|
||||||
|
if is_folder and not vim.endswith(url.path, "/") then
|
||||||
|
table.insert(pieces, "/")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return table.concat(pieces, "")
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param url oil.s3Url
|
||||||
|
---@return boolean
|
||||||
|
local function is_bucket(url)
|
||||||
|
assert(url.bucket and url.bucket ~= "")
|
||||||
|
if url.path then
|
||||||
|
assert(url.path ~= "")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local s3_columns = {}
|
||||||
|
s3_columns.size = {
|
||||||
|
render = function(entry, conf)
|
||||||
|
local meta = entry[FIELD_META]
|
||||||
|
if not meta or not meta.size then
|
||||||
|
return ""
|
||||||
|
elseif meta.size >= 1e9 then
|
||||||
|
return string.format("%.1fG", meta.size / 1e9)
|
||||||
|
elseif meta.size >= 1e6 then
|
||||||
|
return string.format("%.1fM", meta.size / 1e6)
|
||||||
|
elseif meta.size >= 1e3 then
|
||||||
|
return string.format("%.1fk", meta.size / 1e3)
|
||||||
|
else
|
||||||
|
return string.format("%d", meta.size)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
parse = function(line, conf)
|
||||||
|
return line:match("^(%d+%S*)%s+(.*)$")
|
||||||
|
end,
|
||||||
|
|
||||||
|
get_sort_value = function(entry)
|
||||||
|
local meta = entry[FIELD_META]
|
||||||
|
if meta and meta.size then
|
||||||
|
return meta.size
|
||||||
|
else
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
s3_columns.birthtime = {
|
||||||
|
render = function(entry, conf)
|
||||||
|
local meta = entry[FIELD_META]
|
||||||
|
if not meta or not meta.date then
|
||||||
|
return ""
|
||||||
|
else
|
||||||
|
return meta.date
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
parse = function(line, conf)
|
||||||
|
return line:match("^(%d+%-%d+%-%d+%s%d+:%d+:%d+)%s+(.*)$")
|
||||||
|
end,
|
||||||
|
|
||||||
|
get_sort_value = function(entry)
|
||||||
|
local meta = entry[FIELD_META]
|
||||||
|
if meta and meta.date then
|
||||||
|
local year, month, day, hour, min, sec =
|
||||||
|
meta.date:match("^(%d+)%-(%d+)%-(%d+)%s(%d+):(%d+):(%d+)$")
|
||||||
|
local time =
|
||||||
|
os.time({ year = year, month = month, day = day, hour = hour, min = min, sec = sec })
|
||||||
|
return time
|
||||||
|
else
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
---@param name string
|
||||||
|
---@return nil|oil.ColumnDefinition
|
||||||
|
M.get_column = function(name)
|
||||||
|
return s3_columns[name]
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param bufname string
|
||||||
|
---@return string
|
||||||
|
M.get_parent = function(bufname)
|
||||||
|
local res = M.parse_url(bufname)
|
||||||
|
if res.path then
|
||||||
|
assert(res.path ~= "")
|
||||||
|
local path = pathutil.parent(res.path)
|
||||||
|
res.path = path ~= "" and path or nil
|
||||||
|
else
|
||||||
|
res.bucket = nil
|
||||||
|
end
|
||||||
|
return url_to_str(res)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param url string
|
||||||
|
---@param callback fun(url: string)
|
||||||
|
M.normalize_url = function(url, callback)
|
||||||
|
local res = M.parse_url(url)
|
||||||
|
callback(url_to_str(res))
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param url string
|
||||||
|
---@param column_defs string[]
|
||||||
|
---@param callback fun(err?: string, entries?: oil.InternalEntry[], fetch_more?: fun())
|
||||||
|
M.list = function(url, column_defs, callback)
|
||||||
|
if vim.fn.executable("aws") ~= 1 then
|
||||||
|
callback("`aws` is not executable. Can you run `aws s3 ls`?")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local res = M.parse_url(url)
|
||||||
|
s3fs.list_dir(url, url_to_s3(res, true), callback)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param bufnr integer
|
||||||
|
---@return boolean
|
||||||
|
M.is_modifiable = function(bufnr)
|
||||||
|
-- default assumption is that everything is modifiable
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param action oil.Action
|
||||||
|
---@return string
|
||||||
|
M.render_action = function(action)
|
||||||
|
local is_folder = action.entry_type == "directory"
|
||||||
|
if action.type == "create" then
|
||||||
|
local res = M.parse_url(action.url)
|
||||||
|
local extra = is_bucket(res) and "BUCKET " or ""
|
||||||
|
return string.format("CREATE %s%s", extra, url_to_s3(res, is_folder))
|
||||||
|
elseif action.type == "delete" then
|
||||||
|
local res = M.parse_url(action.url)
|
||||||
|
local extra = is_bucket(res) and "BUCKET " or ""
|
||||||
|
return string.format("DELETE %s%s", extra, url_to_s3(res, is_folder))
|
||||||
|
elseif action.type == "move" or action.type == "copy" then
|
||||||
|
local src = action.src_url
|
||||||
|
local dest = action.dest_url
|
||||||
|
if config.get_adapter_by_scheme(src) ~= M then
|
||||||
|
local _, path = util.parse_url(src)
|
||||||
|
assert(path)
|
||||||
|
src = files.to_short_os_path(path, action.entry_type)
|
||||||
|
dest = url_to_s3(M.parse_url(dest), is_folder)
|
||||||
|
elseif config.get_adapter_by_scheme(dest) ~= M then
|
||||||
|
local _, path = util.parse_url(dest)
|
||||||
|
assert(path)
|
||||||
|
dest = files.to_short_os_path(path, action.entry_type)
|
||||||
|
src = url_to_s3(M.parse_url(src), is_folder)
|
||||||
|
end
|
||||||
|
return string.format(" %s %s -> %s", action.type:upper(), src, dest)
|
||||||
|
else
|
||||||
|
error(string.format("Bad action type: '%s'", action.type))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param action oil.Action
|
||||||
|
---@param cb fun(err: nil|string)
|
||||||
|
M.perform_action = function(action, cb)
|
||||||
|
local is_folder = action.entry_type == "directory"
|
||||||
|
if action.type == "create" then
|
||||||
|
local res = M.parse_url(action.url)
|
||||||
|
local bucket = is_bucket(res)
|
||||||
|
|
||||||
|
if action.entry_type == "directory" and bucket then
|
||||||
|
s3fs.mb(url_to_s3(res, true), cb)
|
||||||
|
elseif action.entry_type == "directory" or action.entry_type == "file" then
|
||||||
|
s3fs.touch(url_to_s3(res, is_folder), cb)
|
||||||
|
else
|
||||||
|
cb(string.format("Bad entry type on s3 create action: %s", action.entry_type))
|
||||||
|
end
|
||||||
|
elseif action.type == "delete" then
|
||||||
|
local res = M.parse_url(action.url)
|
||||||
|
local bucket = is_bucket(res)
|
||||||
|
|
||||||
|
if action.entry_type == "directory" and bucket then
|
||||||
|
s3fs.rb(url_to_s3(res, true), cb)
|
||||||
|
elseif action.entry_type == "directory" or action.entry_type == "file" then
|
||||||
|
s3fs.rm(url_to_s3(res, is_folder), is_folder, cb)
|
||||||
|
else
|
||||||
|
cb(string.format("Bad entry type on s3 delete action: %s", action.entry_type))
|
||||||
|
end
|
||||||
|
elseif action.type == "move" then
|
||||||
|
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
||||||
|
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
||||||
|
if
|
||||||
|
(src_adapter ~= M and src_adapter ~= files) or (dest_adapter ~= M and dest_adapter ~= files)
|
||||||
|
then
|
||||||
|
cb(
|
||||||
|
string.format(
|
||||||
|
"We should never attempt to move from the %s adapter to the %s adapter.",
|
||||||
|
src_adapter.name,
|
||||||
|
dest_adapter.name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
local src, _
|
||||||
|
if src_adapter == M then
|
||||||
|
local src_res = M.parse_url(action.src_url)
|
||||||
|
src = url_to_s3(src_res, is_folder)
|
||||||
|
else
|
||||||
|
_, src = util.parse_url(action.src_url)
|
||||||
|
end
|
||||||
|
assert(src)
|
||||||
|
|
||||||
|
local dest
|
||||||
|
if dest_adapter == M then
|
||||||
|
local dest_res = M.parse_url(action.dest_url)
|
||||||
|
dest = url_to_s3(dest_res, is_folder)
|
||||||
|
else
|
||||||
|
_, dest = util.parse_url(action.dest_url)
|
||||||
|
end
|
||||||
|
assert(dest)
|
||||||
|
|
||||||
|
s3fs.mv(src, dest, is_folder, cb)
|
||||||
|
elseif action.type == "copy" then
|
||||||
|
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
||||||
|
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
||||||
|
if
|
||||||
|
(src_adapter ~= M and src_adapter ~= files) or (dest_adapter ~= M and dest_adapter ~= files)
|
||||||
|
then
|
||||||
|
cb(
|
||||||
|
string.format(
|
||||||
|
"We should never attempt to copy from the %s adapter to the %s adapter.",
|
||||||
|
src_adapter.name,
|
||||||
|
dest_adapter.name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
local src, _
|
||||||
|
if src_adapter == M then
|
||||||
|
local src_res = M.parse_url(action.src_url)
|
||||||
|
src = url_to_s3(src_res, is_folder)
|
||||||
|
else
|
||||||
|
_, src = util.parse_url(action.src_url)
|
||||||
|
end
|
||||||
|
assert(src)
|
||||||
|
|
||||||
|
local dest
|
||||||
|
if dest_adapter == M then
|
||||||
|
local dest_res = M.parse_url(action.dest_url)
|
||||||
|
dest = url_to_s3(dest_res, is_folder)
|
||||||
|
else
|
||||||
|
_, dest = util.parse_url(action.dest_url)
|
||||||
|
end
|
||||||
|
assert(dest)
|
||||||
|
|
||||||
|
s3fs.cp(src, dest, is_folder, cb)
|
||||||
|
else
|
||||||
|
cb(string.format("Bad action type: %s", action.type))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M.supported_cross_adapter_actions = { files = "move" }
|
||||||
|
|
||||||
|
---@param bufnr integer
|
||||||
|
M.read_file = function(bufnr)
|
||||||
|
loading.set_loading(bufnr, true)
|
||||||
|
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
||||||
|
local url = M.parse_url(bufname)
|
||||||
|
local basename = pathutil.basename(bufname)
|
||||||
|
local cache_dir = vim.fn.stdpath("cache")
|
||||||
|
assert(type(cache_dir) == "string")
|
||||||
|
local tmpdir = fs.join(cache_dir, "oil")
|
||||||
|
fs.mkdirp(tmpdir)
|
||||||
|
local fd, tmpfile = vim.loop.fs_mkstemp(fs.join(tmpdir, "s3_XXXXXX"))
|
||||||
|
if fd then
|
||||||
|
vim.loop.fs_close(fd)
|
||||||
|
end
|
||||||
|
local tmp_bufnr = vim.fn.bufadd(tmpfile)
|
||||||
|
|
||||||
|
s3fs.cp(url_to_s3(url, false), tmpfile, false, function(err)
|
||||||
|
loading.set_loading(bufnr, false)
|
||||||
|
vim.bo[bufnr].modifiable = true
|
||||||
|
vim.cmd.doautocmd({ args = { "BufReadPre", bufname }, mods = { silent = true } })
|
||||||
|
if err then
|
||||||
|
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, vim.split(err, "\n"))
|
||||||
|
else
|
||||||
|
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, {})
|
||||||
|
vim.api.nvim_buf_call(bufnr, function()
|
||||||
|
vim.cmd.read({ args = { tmpfile }, mods = { silent = true } })
|
||||||
|
end)
|
||||||
|
vim.loop.fs_unlink(tmpfile)
|
||||||
|
vim.api.nvim_buf_set_lines(bufnr, 0, 1, true, {})
|
||||||
|
end
|
||||||
|
vim.bo[bufnr].modified = false
|
||||||
|
local filetype = vim.filetype.match({ buf = bufnr, filename = basename })
|
||||||
|
if filetype then
|
||||||
|
vim.bo[bufnr].filetype = filetype
|
||||||
|
end
|
||||||
|
vim.cmd.doautocmd({ args = { "BufReadPost", bufname }, mods = { silent = true } })
|
||||||
|
vim.api.nvim_buf_delete(tmp_bufnr, { force = true })
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param bufnr integer
|
||||||
|
M.write_file = function(bufnr)
|
||||||
|
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
||||||
|
local url = M.parse_url(bufname)
|
||||||
|
local cache_dir = vim.fn.stdpath("cache")
|
||||||
|
assert(type(cache_dir) == "string")
|
||||||
|
local tmpdir = fs.join(cache_dir, "oil")
|
||||||
|
local fd, tmpfile = vim.loop.fs_mkstemp(fs.join(tmpdir, "s3_XXXXXXXX"))
|
||||||
|
if fd then
|
||||||
|
vim.loop.fs_close(fd)
|
||||||
|
end
|
||||||
|
vim.cmd.doautocmd({ args = { "BufWritePre", bufname }, mods = { silent = true } })
|
||||||
|
vim.bo[bufnr].modifiable = false
|
||||||
|
vim.cmd.write({ args = { tmpfile }, bang = true, mods = { silent = true, noautocmd = true } })
|
||||||
|
local tmp_bufnr = vim.fn.bufadd(tmpfile)
|
||||||
|
|
||||||
|
s3fs.cp(tmpfile, url_to_s3(url, false), false, function(err)
|
||||||
|
vim.bo[bufnr].modifiable = true
|
||||||
|
if err then
|
||||||
|
vim.notify(string.format("Error writing file: %s", err), vim.log.levels.ERROR)
|
||||||
|
else
|
||||||
|
vim.bo[bufnr].modified = false
|
||||||
|
vim.cmd.doautocmd({ args = { "BufWritePost", bufname }, mods = { silent = true } })
|
||||||
|
end
|
||||||
|
vim.loop.fs_unlink(tmpfile)
|
||||||
|
vim.api.nvim_buf_delete(tmp_bufnr, { force = true })
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
149
lua/oil/adapters/s3/s3fs.lua
Normal file
149
lua/oil/adapters/s3/s3fs.lua
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
local cache = require("oil.cache")
|
||||||
|
local config = require("oil.config")
|
||||||
|
local constants = require("oil.constants")
|
||||||
|
local shell = require("oil.shell")
|
||||||
|
local util = require("oil.util")
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local FIELD_META = constants.FIELD_META
|
||||||
|
|
||||||
|
---@param line string
|
||||||
|
---@return string Name of entry
|
||||||
|
---@return oil.EntryType
|
||||||
|
---@return table Metadata for entry
|
||||||
|
local function parse_ls_line_bucket(line)
|
||||||
|
local date, name = line:match("^(%d+%-%d+%-%d+%s%d+:%d+:%d+)%s+(.*)$")
|
||||||
|
if not date or not name then
|
||||||
|
error(string.format("Could not parse '%s'", line))
|
||||||
|
end
|
||||||
|
local type = "directory"
|
||||||
|
local meta = { date = date }
|
||||||
|
return name, type, meta
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param line string
|
||||||
|
---@return string Name of entry
|
||||||
|
---@return oil.EntryType
|
||||||
|
---@return table Metadata for entry
|
||||||
|
local function parse_ls_line_file(line)
|
||||||
|
local name = line:match("^%s+PRE%s+(.*)/$")
|
||||||
|
local type = "directory"
|
||||||
|
local meta = {}
|
||||||
|
if name then
|
||||||
|
return name, type, meta
|
||||||
|
end
|
||||||
|
local date, size
|
||||||
|
date, size, name = line:match("^(%d+%-%d+%-%d+%s%d+:%d+:%d+)%s+(%d+)%s+(.*)$")
|
||||||
|
if not name then
|
||||||
|
error(string.format("Could not parse '%s'", line))
|
||||||
|
end
|
||||||
|
type = "file"
|
||||||
|
meta = { date = date, size = tonumber(size) }
|
||||||
|
return name, type, meta
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param cmd string[] cmd and flags
|
||||||
|
---@return string[] Shell command to run
|
||||||
|
local function create_s3_command(cmd)
|
||||||
|
local full_cmd = vim.list_extend({ "aws", "s3" }, cmd)
|
||||||
|
return vim.list_extend(full_cmd, config.extra_s3_args)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param url string
|
||||||
|
---@param path string
|
||||||
|
---@param callback fun(err?: string, entries?: oil.InternalEntry[], fetch_more?: fun())
|
||||||
|
function M.list_dir(url, path, callback)
|
||||||
|
local cmd = create_s3_command({ "ls", path, "--color=off", "--no-cli-pager" })
|
||||||
|
shell.run(cmd, function(err, lines)
|
||||||
|
if err then
|
||||||
|
return callback(err)
|
||||||
|
end
|
||||||
|
assert(lines)
|
||||||
|
local cache_entries = {}
|
||||||
|
local url_path, _
|
||||||
|
_, url_path = util.parse_url(url)
|
||||||
|
local is_top_level = url_path == nil or url_path:match("/") == nil
|
||||||
|
local parse_ls_line = is_top_level and parse_ls_line_bucket or parse_ls_line_file
|
||||||
|
for _, line in ipairs(lines) do
|
||||||
|
if line ~= "" then
|
||||||
|
local name, type, meta = parse_ls_line(line)
|
||||||
|
-- in s3 '-' can be used to create an "empty folder"
|
||||||
|
if name ~= "-" then
|
||||||
|
local cache_entry = cache.create_entry(url, name, type)
|
||||||
|
table.insert(cache_entries, cache_entry)
|
||||||
|
cache_entry[FIELD_META] = meta
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
callback(nil, cache_entries)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Create files
|
||||||
|
---@param path string
|
||||||
|
---@param callback fun(err: nil|string)
|
||||||
|
function M.touch(path, callback)
|
||||||
|
-- here "-" means that we copy from stdin
|
||||||
|
local cmd = create_s3_command({ "cp", "-", path })
|
||||||
|
shell.run(cmd, { stdin = "null" }, callback)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Remove files
|
||||||
|
---@param path string
|
||||||
|
---@param is_folder boolean
|
||||||
|
---@param callback fun(err: nil|string)
|
||||||
|
function M.rm(path, is_folder, callback)
|
||||||
|
local main_cmd = { "rm", path }
|
||||||
|
if is_folder then
|
||||||
|
table.insert(main_cmd, "--recursive")
|
||||||
|
end
|
||||||
|
local cmd = create_s3_command(main_cmd)
|
||||||
|
shell.run(cmd, callback)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Remove bucket
|
||||||
|
---@param bucket string
|
||||||
|
---@param callback fun(err: nil|string)
|
||||||
|
function M.rb(bucket, callback)
|
||||||
|
local cmd = create_s3_command({ "rb", bucket })
|
||||||
|
shell.run(cmd, callback)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Make bucket
|
||||||
|
---@param bucket string
|
||||||
|
---@param callback fun(err: nil|string)
|
||||||
|
function M.mb(bucket, callback)
|
||||||
|
local cmd = create_s3_command({ "mb", bucket })
|
||||||
|
shell.run(cmd, callback)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Move files
|
||||||
|
---@param src string
|
||||||
|
---@param dest string
|
||||||
|
---@param is_folder boolean
|
||||||
|
---@param callback fun(err: nil|string)
|
||||||
|
function M.mv(src, dest, is_folder, callback)
|
||||||
|
local main_cmd = { "mv", src, dest }
|
||||||
|
if is_folder then
|
||||||
|
table.insert(main_cmd, "--recursive")
|
||||||
|
end
|
||||||
|
local cmd = create_s3_command(main_cmd)
|
||||||
|
shell.run(cmd, callback)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Copy files
|
||||||
|
---@param src string
|
||||||
|
---@param dest string
|
||||||
|
---@param is_folder boolean
|
||||||
|
---@param callback fun(err: nil|string)
|
||||||
|
function M.cp(src, dest, is_folder, callback)
|
||||||
|
local main_cmd = { "cp", src, dest }
|
||||||
|
if is_folder then
|
||||||
|
table.insert(main_cmd, "--recursive")
|
||||||
|
end
|
||||||
|
local cmd = create_s3_command(main_cmd)
|
||||||
|
shell.run(cmd, callback)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
|
|
@ -126,7 +126,7 @@ ssh_columns.permissions = {
|
||||||
|
|
||||||
compare = function(entry, parsed_value)
|
compare = function(entry, parsed_value)
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
if parsed_value and meta.mode then
|
if parsed_value and meta and meta.mode then
|
||||||
local mask = bit.lshift(1, 12) - 1
|
local mask = bit.lshift(1, 12) - 1
|
||||||
local old_mode = bit.band(meta.mode, mask)
|
local old_mode = bit.band(meta.mode, mask)
|
||||||
if parsed_value ~= old_mode then
|
if parsed_value ~= old_mode then
|
||||||
|
|
@ -169,7 +169,7 @@ ssh_columns.size = {
|
||||||
|
|
||||||
get_sort_value = function(entry)
|
get_sort_value = function(entry)
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
if meta.size then
|
if meta and meta.size then
|
||||||
return meta.size
|
return meta.size
|
||||||
else
|
else
|
||||||
return 0
|
return 0
|
||||||
|
|
@ -303,8 +303,8 @@ M.perform_action = function(action, cb)
|
||||||
local conn = get_connection(action.url)
|
local conn = get_connection(action.url)
|
||||||
conn:rm(res.path, cb)
|
conn:rm(res.path, cb)
|
||||||
elseif action.type == "move" then
|
elseif action.type == "move" then
|
||||||
local src_adapter = config.get_adapter_by_scheme(action.src_url)
|
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
||||||
local dest_adapter = config.get_adapter_by_scheme(action.dest_url)
|
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
||||||
if src_adapter == M and dest_adapter == M then
|
if src_adapter == M and dest_adapter == M then
|
||||||
local src_res = M.parse_url(action.src_url)
|
local src_res = M.parse_url(action.src_url)
|
||||||
local dest_res = M.parse_url(action.dest_url)
|
local dest_res = M.parse_url(action.dest_url)
|
||||||
|
|
@ -324,8 +324,8 @@ M.perform_action = function(action, cb)
|
||||||
cb("We should never attempt to move across adapters")
|
cb("We should never attempt to move across adapters")
|
||||||
end
|
end
|
||||||
elseif action.type == "copy" then
|
elseif action.type == "copy" then
|
||||||
local src_adapter = config.get_adapter_by_scheme(action.src_url)
|
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
||||||
local dest_adapter = config.get_adapter_by_scheme(action.dest_url)
|
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
||||||
if src_adapter == M and dest_adapter == M then
|
if src_adapter == M and dest_adapter == M then
|
||||||
local src_res = M.parse_url(action.src_url)
|
local src_res = M.parse_url(action.src_url)
|
||||||
local dest_res = M.parse_url(action.dest_url)
|
local dest_res = M.parse_url(action.dest_url)
|
||||||
|
|
|
||||||
|
|
@ -42,10 +42,17 @@ local function parse_ls_line(line)
|
||||||
local name, size, date, major, minor
|
local name, size, date, major, minor
|
||||||
if typechar == "c" or typechar == "b" then
|
if typechar == "c" or typechar == "b" then
|
||||||
major, minor, date, name = rem:match("^(%d+)%s*,%s*(%d+)%s+(%S+%s+%d+%s+%d%d:?%d%d)%s+(.*)")
|
major, minor, date, name = rem:match("^(%d+)%s*,%s*(%d+)%s+(%S+%s+%d+%s+%d%d:?%d%d)%s+(.*)")
|
||||||
|
if name == nil then
|
||||||
|
major, minor, date, name =
|
||||||
|
rem:match("^(%d+)%s*,%s*(%d+)%s+(%d+%-%d+%-%d+%s+%d%d:?%d%d)%s+(.*)")
|
||||||
|
end
|
||||||
meta.major = tonumber(major)
|
meta.major = tonumber(major)
|
||||||
meta.minor = tonumber(minor)
|
meta.minor = tonumber(minor)
|
||||||
else
|
else
|
||||||
size, date, name = rem:match("^(%d+)%s+(%S+%s+%d+%s+%d%d:?%d%d)%s+(.*)")
|
size, date, name = rem:match("^(%d+)%s+(%S+%s+%d+%s+%d%d:?%d%d)%s+(.*)")
|
||||||
|
if name == nil then
|
||||||
|
size, date, name = rem:match("^(%d+)%s+(%d+%-%d+%-%d+%s+%d%d:?%d%d)%s+(.*)")
|
||||||
|
end
|
||||||
meta.size = tonumber(size)
|
meta.size = tonumber(size)
|
||||||
end
|
end
|
||||||
meta.iso_modified_date = date
|
meta.iso_modified_date = date
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,7 @@ end
|
||||||
---@param cb fun(path: string)
|
---@param cb fun(path: string)
|
||||||
M.get_entry_path = function(url, entry, cb)
|
M.get_entry_path = function(url, entry, cb)
|
||||||
local internal_entry = assert(cache.get_entry_by_id(entry.id))
|
local internal_entry = assert(cache.get_entry_by_id(entry.id))
|
||||||
local meta = internal_entry[FIELD_META]
|
local meta = assert(internal_entry[FIELD_META])
|
||||||
---@type oil.TrashInfo
|
---@type oil.TrashInfo
|
||||||
local trash_info = meta.trash_info
|
local trash_info = meta.trash_info
|
||||||
if not trash_info then
|
if not trash_info then
|
||||||
|
|
@ -151,7 +151,7 @@ end
|
||||||
---@field info_file string
|
---@field info_file string
|
||||||
---@field original_path string
|
---@field original_path string
|
||||||
---@field deletion_date number
|
---@field deletion_date number
|
||||||
---@field stat uv_fs_t
|
---@field stat uv.aliases.fs_stat_table
|
||||||
|
|
||||||
---@param info_file string
|
---@param info_file string
|
||||||
---@param cb fun(err?: string, info?: oil.TrashInfo)
|
---@param cb fun(err?: string, info?: oil.TrashInfo)
|
||||||
|
|
@ -381,7 +381,7 @@ file_columns.mtime = {
|
||||||
get_sort_value = function(entry)
|
get_sort_value = function(entry)
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
---@type nil|oil.TrashInfo
|
---@type nil|oil.TrashInfo
|
||||||
local trash_info = meta.trash_info
|
local trash_info = meta and meta.trash_info
|
||||||
if trash_info then
|
if trash_info then
|
||||||
return trash_info.deletion_date
|
return trash_info.deletion_date
|
||||||
else
|
else
|
||||||
|
|
@ -417,7 +417,7 @@ M.filter_action = function(action)
|
||||||
elseif action.type == "delete" then
|
elseif action.type == "delete" then
|
||||||
local entry = assert(cache.get_entry_by_url(action.url))
|
local entry = assert(cache.get_entry_by_url(action.url))
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
return meta.trash_info ~= nil
|
return meta ~= nil and meta.trash_info ~= nil
|
||||||
elseif action.type == "move" then
|
elseif action.type == "move" then
|
||||||
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
||||||
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
||||||
|
|
@ -447,7 +447,7 @@ M.render_action = function(action)
|
||||||
local entry = assert(cache.get_entry_by_url(action.url))
|
local entry = assert(cache.get_entry_by_url(action.url))
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
---@type oil.TrashInfo
|
---@type oil.TrashInfo
|
||||||
local trash_info = meta.trash_info
|
local trash_info = assert(meta).trash_info
|
||||||
local short_path = fs.shorten_path(trash_info.original_path)
|
local short_path = fs.shorten_path(trash_info.original_path)
|
||||||
return string.format(" PURGE %s", short_path)
|
return string.format(" PURGE %s", short_path)
|
||||||
elseif action.type == "move" then
|
elseif action.type == "move" then
|
||||||
|
|
@ -561,7 +561,7 @@ M.perform_action = function(action, cb)
|
||||||
local entry = assert(cache.get_entry_by_url(action.url))
|
local entry = assert(cache.get_entry_by_url(action.url))
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
---@type oil.TrashInfo
|
---@type oil.TrashInfo
|
||||||
local trash_info = meta.trash_info
|
local trash_info = assert(meta).trash_info
|
||||||
purge(trash_info, cb)
|
purge(trash_info, cb)
|
||||||
elseif action.type == "move" then
|
elseif action.type == "move" then
|
||||||
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
||||||
|
|
@ -576,7 +576,7 @@ M.perform_action = function(action, cb)
|
||||||
local entry = assert(cache.get_entry_by_url(action.src_url))
|
local entry = assert(cache.get_entry_by_url(action.src_url))
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
---@type oil.TrashInfo
|
---@type oil.TrashInfo
|
||||||
local trash_info = meta.trash_info
|
local trash_info = assert(meta).trash_info
|
||||||
fs.recursive_move(action.entry_type, trash_info.trash_file, dest_path, function(err)
|
fs.recursive_move(action.entry_type, trash_info.trash_file, dest_path, function(err)
|
||||||
if err then
|
if err then
|
||||||
return cb(err)
|
return cb(err)
|
||||||
|
|
@ -596,8 +596,7 @@ M.perform_action = function(action, cb)
|
||||||
if err then
|
if err then
|
||||||
cb(err)
|
cb(err)
|
||||||
else
|
else
|
||||||
---@diagnostic disable-next-line: undefined-field
|
local stat_type = trash_info.stat.type or "unknown"
|
||||||
local stat_type = trash_info.stat.type
|
|
||||||
fs.recursive_copy(stat_type, path, trash_info.trash_file, vim.schedule_wrap(cb))
|
fs.recursive_copy(stat_type, path, trash_info.trash_file, vim.schedule_wrap(cb))
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
@ -608,7 +607,7 @@ M.perform_action = function(action, cb)
|
||||||
local entry = assert(cache.get_entry_by_url(action.src_url))
|
local entry = assert(cache.get_entry_by_url(action.src_url))
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
---@type oil.TrashInfo
|
---@type oil.TrashInfo
|
||||||
local trash_info = meta.trash_info
|
local trash_info = assert(meta).trash_info
|
||||||
fs.recursive_copy(action.entry_type, trash_info.trash_file, dest_path, cb)
|
fs.recursive_copy(action.entry_type, trash_info.trash_file, dest_path, cb)
|
||||||
else
|
else
|
||||||
error("Must be moving files into or out of trash")
|
error("Must be moving files into or out of trash")
|
||||||
|
|
@ -625,8 +624,7 @@ M.delete_to_trash = function(path, cb)
|
||||||
if err then
|
if err then
|
||||||
cb(err)
|
cb(err)
|
||||||
else
|
else
|
||||||
---@diagnostic disable-next-line: undefined-field
|
local stat_type = trash_info.stat.type or "unknown"
|
||||||
local stat_type = trash_info.stat.type
|
|
||||||
fs.recursive_move(stat_type, path, trash_info.trash_file, vim.schedule_wrap(cb))
|
fs.recursive_move(stat_type, path, trash_info.trash_file, vim.schedule_wrap(cb))
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
|
||||||
|
|
@ -224,7 +224,6 @@ M.delete_to_trash = function(path, cb)
|
||||||
end
|
end
|
||||||
|
|
||||||
local stat_type = src_stat.type
|
local stat_type = src_stat.type
|
||||||
---@cast stat_type oil.EntryType
|
|
||||||
fs.recursive_move(stat_type, path, dest, vim.schedule_wrap(cb))
|
fs.recursive_move(stat_type, path, dest, vim.schedule_wrap(cb))
|
||||||
end)
|
end)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -37,10 +37,10 @@ local win_addslash = function(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class oil.WindowsTrashInfo
|
---@class oil.WindowsTrashInfo
|
||||||
---@field trash_file string?
|
---@field trash_file string
|
||||||
---@field original_path string?
|
---@field original_path string
|
||||||
---@field deletion_date string?
|
---@field deletion_date integer
|
||||||
---@field info_file string?
|
---@field info_file? string
|
||||||
|
|
||||||
---@param url string
|
---@param url string
|
||||||
---@param column_defs string[]
|
---@param column_defs string[]
|
||||||
|
|
@ -96,6 +96,7 @@ M.list = function(url, column_defs, cb)
|
||||||
end
|
end
|
||||||
cache_entry[FIELD_META] = {
|
cache_entry[FIELD_META] = {
|
||||||
stat = nil,
|
stat = nil,
|
||||||
|
---@type oil.WindowsTrashInfo
|
||||||
trash_info = {
|
trash_info = {
|
||||||
trash_file = entry.Path,
|
trash_file = entry.Path,
|
||||||
original_path = entry.OriginalPath,
|
original_path = entry.OriginalPath,
|
||||||
|
|
@ -164,8 +165,8 @@ file_columns.mtime = {
|
||||||
|
|
||||||
get_sort_value = function(entry)
|
get_sort_value = function(entry)
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
---@type oil.WindowsTrashInfo
|
---@type nil|oil.WindowsTrashInfo
|
||||||
local trash_info = meta.trash_info
|
local trash_info = meta and meta.trash_info
|
||||||
if trash_info and trash_info.deletion_date then
|
if trash_info and trash_info.deletion_date then
|
||||||
return trash_info.deletion_date
|
return trash_info.deletion_date
|
||||||
else
|
else
|
||||||
|
|
@ -199,7 +200,7 @@ M.filter_action = function(action)
|
||||||
elseif action.type == "delete" then
|
elseif action.type == "delete" then
|
||||||
local entry = assert(cache.get_entry_by_url(action.url))
|
local entry = assert(cache.get_entry_by_url(action.url))
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
return meta.trash_info ~= nil
|
return meta ~= nil and meta.trash_info ~= nil
|
||||||
elseif action.type == "move" then
|
elseif action.type == "move" then
|
||||||
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
||||||
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
||||||
|
|
@ -235,7 +236,7 @@ end
|
||||||
M.get_entry_path = function(url, entry, cb)
|
M.get_entry_path = function(url, entry, cb)
|
||||||
local internal_entry = assert(cache.get_entry_by_id(entry.id))
|
local internal_entry = assert(cache.get_entry_by_id(entry.id))
|
||||||
local meta = internal_entry[FIELD_META] --[[@as {stat: uv_fs_t, trash_info: oil.WindowsTrashInfo, display_name: string}]]
|
local meta = internal_entry[FIELD_META] --[[@as {stat: uv_fs_t, trash_info: oil.WindowsTrashInfo, display_name: string}]]
|
||||||
local trash_info = meta.trash_info
|
local trash_info = meta and meta.trash_info
|
||||||
if not trash_info then
|
if not trash_info then
|
||||||
-- This is a subpath in the trash
|
-- This is a subpath in the trash
|
||||||
M.normalize_url(url, cb)
|
M.normalize_url(url, cb)
|
||||||
|
|
@ -265,7 +266,7 @@ M.render_action = function(action)
|
||||||
local entry = assert(cache.get_entry_by_url(action.url))
|
local entry = assert(cache.get_entry_by_url(action.url))
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
---@type oil.WindowsTrashInfo
|
---@type oil.WindowsTrashInfo
|
||||||
local trash_info = meta.trash_info
|
local trash_info = assert(meta).trash_info
|
||||||
local short_path = fs.shorten_path(trash_info.original_path)
|
local short_path = fs.shorten_path(trash_info.original_path)
|
||||||
return string.format(" PURGE %s", short_path)
|
return string.format(" PURGE %s", short_path)
|
||||||
elseif action.type == "move" then
|
elseif action.type == "move" then
|
||||||
|
|
@ -348,7 +349,7 @@ M.perform_action = function(action, cb)
|
||||||
if action.type == "delete" then
|
if action.type == "delete" then
|
||||||
local entry = assert(cache.get_entry_by_url(action.url))
|
local entry = assert(cache.get_entry_by_url(action.url))
|
||||||
local meta = entry[FIELD_META] --[[@as {stat: uv_fs_t, trash_info: oil.WindowsTrashInfo, display_name: string}]]
|
local meta = entry[FIELD_META] --[[@as {stat: uv_fs_t, trash_info: oil.WindowsTrashInfo, display_name: string}]]
|
||||||
local trash_info = meta.trash_info
|
local trash_info = meta and meta.trash_info
|
||||||
|
|
||||||
purge(trash_info, cb)
|
purge(trash_info, cb)
|
||||||
elseif action.type == "move" then
|
elseif action.type == "move" then
|
||||||
|
|
@ -364,7 +365,7 @@ M.perform_action = function(action, cb)
|
||||||
dest_path = fs.posix_to_os_path(dest_path)
|
dest_path = fs.posix_to_os_path(dest_path)
|
||||||
local entry = assert(cache.get_entry_by_url(action.src_url))
|
local entry = assert(cache.get_entry_by_url(action.src_url))
|
||||||
local meta = entry[FIELD_META] --[[@as {stat: uv_fs_t, trash_info: oil.WindowsTrashInfo, display_name: string}]]
|
local meta = entry[FIELD_META] --[[@as {stat: uv_fs_t, trash_info: oil.WindowsTrashInfo, display_name: string}]]
|
||||||
local trash_info = meta.trash_info
|
local trash_info = meta and meta.trash_info
|
||||||
fs.recursive_move(action.entry_type, trash_info.trash_file, dest_path, function(err)
|
fs.recursive_move(action.entry_type, trash_info.trash_file, dest_path, function(err)
|
||||||
if err then
|
if err then
|
||||||
return cb(err)
|
return cb(err)
|
||||||
|
|
@ -388,7 +389,7 @@ M.perform_action = function(action, cb)
|
||||||
dest_path = fs.posix_to_os_path(dest_path)
|
dest_path = fs.posix_to_os_path(dest_path)
|
||||||
local entry = assert(cache.get_entry_by_url(action.src_url))
|
local entry = assert(cache.get_entry_by_url(action.src_url))
|
||||||
local meta = entry[FIELD_META] --[[@as {stat: uv_fs_t, trash_info: oil.WindowsTrashInfo, display_name: string}]]
|
local meta = entry[FIELD_META] --[[@as {stat: uv_fs_t, trash_info: oil.WindowsTrashInfo, display_name: string}]]
|
||||||
local trash_info = meta.trash_info
|
local trash_info = meta and meta.trash_info
|
||||||
fs.recursive_copy(action.entry_type, trash_info.trash_file, dest_path, cb)
|
fs.recursive_copy(action.entry_type, trash_info.trash_file, dest_path, cb)
|
||||||
else
|
else
|
||||||
error("Must be moving files into or out of trash")
|
error("Must be moving files into or out of trash")
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,16 @@ end
|
||||||
|
|
||||||
---@param init_command? string
|
---@param init_command? string
|
||||||
function PowershellConnection:_init(init_command)
|
function PowershellConnection:_init(init_command)
|
||||||
|
-- For some reason beyond my understanding, at least one of the following
|
||||||
|
-- things requires `noshellslash` to avoid the embeded powershell process to
|
||||||
|
-- send only "" to the stdout (never calling the callback because
|
||||||
|
-- "===DONE(True)===" is never sent to stdout)
|
||||||
|
-- * vim.fn.jobstart
|
||||||
|
-- * cmd.exe
|
||||||
|
-- * powershell.exe
|
||||||
|
local saved_shellslash = vim.o.shellslash
|
||||||
|
vim.o.shellslash = false
|
||||||
|
|
||||||
-- 65001 is the UTF-8 codepage
|
-- 65001 is the UTF-8 codepage
|
||||||
-- powershell needs to be launched with the UTF-8 codepage to use it for both stdin and stdout
|
-- powershell needs to be launched with the UTF-8 codepage to use it for both stdin and stdout
|
||||||
local jid = vim.fn.jobstart({
|
local jid = vim.fn.jobstart({
|
||||||
|
|
@ -57,6 +67,7 @@ function PowershellConnection:_init(init_command)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
vim.o.shellslash = saved_shellslash
|
||||||
|
|
||||||
if jid == 0 then
|
if jid == 0 then
|
||||||
self:_set_error("passed invalid arguments to 'powershell'")
|
self:_set_error("passed invalid arguments to 'powershell'")
|
||||||
|
|
|
||||||
370
lua/oil/clipboard.lua
Normal file
370
lua/oil/clipboard.lua
Normal file
|
|
@ -0,0 +1,370 @@
|
||||||
|
local cache = require("oil.cache")
|
||||||
|
local columns = require("oil.columns")
|
||||||
|
local config = require("oil.config")
|
||||||
|
local fs = require("oil.fs")
|
||||||
|
local oil = require("oil")
|
||||||
|
local parser = require("oil.mutator.parser")
|
||||||
|
local util = require("oil.util")
|
||||||
|
local view = require("oil.view")
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
---@return "wayland"|"x11"|nil
|
||||||
|
local function get_linux_session_type()
|
||||||
|
local xdg_session_type = vim.env.XDG_SESSION_TYPE
|
||||||
|
if not xdg_session_type then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
xdg_session_type = xdg_session_type:lower()
|
||||||
|
if xdg_session_type:find("x11") then
|
||||||
|
return "x11"
|
||||||
|
elseif xdg_session_type:find("wayland") then
|
||||||
|
return "wayland"
|
||||||
|
else
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return boolean
|
||||||
|
local function is_linux_desktop_gnome()
|
||||||
|
local cur_desktop = vim.env.XDG_CURRENT_DESKTOP
|
||||||
|
local session_desktop = vim.env.XDG_SESSION_DESKTOP
|
||||||
|
local idx = session_desktop and session_desktop:lower():find("gnome")
|
||||||
|
or cur_desktop and cur_desktop:lower():find("gnome")
|
||||||
|
return idx ~= nil or cur_desktop == "X-Cinnamon" or cur_desktop == "XFCE"
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param winid integer
|
||||||
|
---@param entry oil.InternalEntry
|
||||||
|
---@param column_defs oil.ColumnSpec[]
|
||||||
|
---@param adapter oil.Adapter
|
||||||
|
---@param bufnr integer
|
||||||
|
local function write_pasted(winid, entry, column_defs, adapter, bufnr)
|
||||||
|
local col_width = {}
|
||||||
|
for i in ipairs(column_defs) do
|
||||||
|
col_width[i + 1] = 1
|
||||||
|
end
|
||||||
|
local line_table =
|
||||||
|
{ view.format_entry_cols(entry, column_defs, col_width, adapter, false, bufnr) }
|
||||||
|
local lines, _ = util.render_table(line_table, col_width)
|
||||||
|
local pos = vim.api.nvim_win_get_cursor(winid)
|
||||||
|
vim.api.nvim_buf_set_lines(bufnr, pos[1], pos[1], true, lines)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param parent_url string
|
||||||
|
---@param entry oil.InternalEntry
|
||||||
|
local function remove_entry_from_parent_buffer(parent_url, entry)
|
||||||
|
local bufnr = vim.fn.bufadd(parent_url)
|
||||||
|
assert(vim.api.nvim_buf_is_loaded(bufnr), "Expected parent buffer to be loaded during paste")
|
||||||
|
local adapter = assert(util.get_adapter(bufnr))
|
||||||
|
local column_defs = columns.get_supported_columns(adapter)
|
||||||
|
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
||||||
|
for i, line in ipairs(lines) do
|
||||||
|
local result = parser.parse_line(adapter, line, column_defs)
|
||||||
|
if result and result.entry == entry then
|
||||||
|
vim.api.nvim_buf_set_lines(bufnr, i - 1, i, false, {})
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local exported = util.export_entry(entry)
|
||||||
|
vim.notify(
|
||||||
|
string.format("Error: could not delete original file '%s'", exported.name),
|
||||||
|
vim.log.levels.ERROR
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param paths string[]
|
||||||
|
---@param delete_original? boolean
|
||||||
|
local function paste_paths(paths, delete_original)
|
||||||
|
local bufnr = vim.api.nvim_get_current_buf()
|
||||||
|
local scheme = "oil://"
|
||||||
|
local adapter = assert(config.get_adapter_by_scheme(scheme))
|
||||||
|
local column_defs = columns.get_supported_columns(scheme)
|
||||||
|
local winid = vim.api.nvim_get_current_win()
|
||||||
|
|
||||||
|
local parent_urls = {}
|
||||||
|
local pending_paths = {}
|
||||||
|
|
||||||
|
-- Handle as many paths synchronously as possible
|
||||||
|
for _, path in ipairs(paths) do
|
||||||
|
-- Trim the trailing slash off directories
|
||||||
|
if vim.endswith(path, "/") then
|
||||||
|
path = path:sub(1, -2)
|
||||||
|
end
|
||||||
|
|
||||||
|
local ori_entry = cache.get_entry_by_url(scheme .. path)
|
||||||
|
local parent_url = util.addslash(scheme .. vim.fs.dirname(path))
|
||||||
|
if ori_entry then
|
||||||
|
write_pasted(winid, ori_entry, column_defs, adapter, bufnr)
|
||||||
|
if delete_original then
|
||||||
|
remove_entry_from_parent_buffer(parent_url, ori_entry)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
parent_urls[parent_url] = true
|
||||||
|
table.insert(pending_paths, path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- If all paths could be handled synchronously, we're done
|
||||||
|
if #pending_paths == 0 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Process the remaining paths by asynchronously loading them
|
||||||
|
local cursor = vim.api.nvim_win_get_cursor(winid)
|
||||||
|
local complete_loading = util.cb_collect(#vim.tbl_keys(parent_urls), function(err)
|
||||||
|
if err then
|
||||||
|
vim.notify(string.format("Error loading parent directory: %s", err), vim.log.levels.ERROR)
|
||||||
|
else
|
||||||
|
-- Something in this process moves the cursor to the top of the window, so have to restore it
|
||||||
|
vim.api.nvim_win_set_cursor(winid, cursor)
|
||||||
|
|
||||||
|
for _, path in ipairs(pending_paths) do
|
||||||
|
local ori_entry = cache.get_entry_by_url(scheme .. path)
|
||||||
|
if ori_entry then
|
||||||
|
write_pasted(winid, ori_entry, column_defs, adapter, bufnr)
|
||||||
|
if delete_original then
|
||||||
|
local parent_url = util.addslash(scheme .. vim.fs.dirname(path))
|
||||||
|
remove_entry_from_parent_buffer(parent_url, ori_entry)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
vim.notify(
|
||||||
|
string.format("The pasted file '%s' could not be found", path),
|
||||||
|
vim.log.levels.ERROR
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
for parent_url, _ in pairs(parent_urls) do
|
||||||
|
local new_bufnr = vim.api.nvim_create_buf(false, false)
|
||||||
|
vim.api.nvim_buf_set_name(new_bufnr, parent_url)
|
||||||
|
oil.load_oil_buffer(new_bufnr)
|
||||||
|
util.run_after_load(new_bufnr, complete_loading)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return integer start
|
||||||
|
---@return integer end
|
||||||
|
local function range_from_selection()
|
||||||
|
-- [bufnum, lnum, col, off]; both row and column 1-indexed
|
||||||
|
local start = vim.fn.getpos("v")
|
||||||
|
local end_ = vim.fn.getpos(".")
|
||||||
|
local start_row = start[2]
|
||||||
|
local end_row = end_[2]
|
||||||
|
|
||||||
|
if start_row > end_row then
|
||||||
|
start_row, end_row = end_row, start_row
|
||||||
|
end
|
||||||
|
|
||||||
|
return start_row, end_row
|
||||||
|
end
|
||||||
|
|
||||||
|
M.copy_to_system_clipboard = function()
|
||||||
|
local dir = oil.get_current_dir()
|
||||||
|
if not dir then
|
||||||
|
vim.notify("System clipboard only works for local files", vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local entries = {}
|
||||||
|
local mode = vim.api.nvim_get_mode().mode
|
||||||
|
if mode == "v" or mode == "V" then
|
||||||
|
if fs.is_mac then
|
||||||
|
vim.notify(
|
||||||
|
"Copying multiple paths to clipboard is not supported on mac",
|
||||||
|
vim.log.levels.ERROR
|
||||||
|
)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local start_row, end_row = range_from_selection()
|
||||||
|
for i = start_row, end_row do
|
||||||
|
table.insert(entries, oil.get_entry_on_line(0, i))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- leave visual mode
|
||||||
|
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("<Esc>", true, false, true), "n", true)
|
||||||
|
else
|
||||||
|
table.insert(entries, oil.get_cursor_entry())
|
||||||
|
end
|
||||||
|
|
||||||
|
-- This removes holes in the list-like table
|
||||||
|
entries = vim.tbl_values(entries)
|
||||||
|
|
||||||
|
if #entries == 0 then
|
||||||
|
vim.notify("Could not find local file under cursor", vim.log.levels.WARN)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local paths = {}
|
||||||
|
for _, entry in ipairs(entries) do
|
||||||
|
table.insert(paths, dir .. entry.name)
|
||||||
|
end
|
||||||
|
local cmd = {}
|
||||||
|
local stdin
|
||||||
|
if fs.is_mac then
|
||||||
|
cmd = {
|
||||||
|
"osascript",
|
||||||
|
"-e",
|
||||||
|
"on run args",
|
||||||
|
"-e",
|
||||||
|
"set the clipboard to POSIX file (first item of args)",
|
||||||
|
"-e",
|
||||||
|
"end run",
|
||||||
|
paths[1],
|
||||||
|
}
|
||||||
|
elseif fs.is_linux then
|
||||||
|
local xdg_session_type = get_linux_session_type()
|
||||||
|
if xdg_session_type == "x11" then
|
||||||
|
vim.list_extend(cmd, { "xclip", "-i", "-selection", "clipboard" })
|
||||||
|
elseif xdg_session_type == "wayland" then
|
||||||
|
table.insert(cmd, "wl-copy")
|
||||||
|
else
|
||||||
|
vim.notify("System clipboard not supported, check $XDG_SESSION_TYPE", vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local urls = {}
|
||||||
|
for _, path in ipairs(paths) do
|
||||||
|
table.insert(urls, "file://" .. path)
|
||||||
|
end
|
||||||
|
if is_linux_desktop_gnome() then
|
||||||
|
stdin = string.format("copy\n%s\0", table.concat(urls, "\n"))
|
||||||
|
vim.list_extend(cmd, { "-t", "x-special/gnome-copied-files" })
|
||||||
|
else
|
||||||
|
stdin = table.concat(urls, "\n") .. "\n"
|
||||||
|
vim.list_extend(cmd, { "-t", "text/uri-list" })
|
||||||
|
end
|
||||||
|
else
|
||||||
|
vim.notify("System clipboard not supported on Windows", vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if vim.fn.executable(cmd[1]) == 0 then
|
||||||
|
vim.notify(string.format("Could not find executable '%s'", cmd[1]), vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local stderr = ""
|
||||||
|
local jid = vim.fn.jobstart(cmd, {
|
||||||
|
stderr_buffered = true,
|
||||||
|
on_stderr = function(_, data)
|
||||||
|
stderr = table.concat(data, "\n")
|
||||||
|
end,
|
||||||
|
on_exit = function(j, exit_code)
|
||||||
|
if exit_code ~= 0 then
|
||||||
|
vim.notify(
|
||||||
|
string.format("Error copying '%s' to system clipboard\n%s", vim.inspect(paths), stderr),
|
||||||
|
vim.log.levels.ERROR
|
||||||
|
)
|
||||||
|
else
|
||||||
|
if #paths == 1 then
|
||||||
|
vim.notify(string.format("Copied '%s' to system clipboard", paths[1]))
|
||||||
|
else
|
||||||
|
vim.notify(string.format("Copied %d files to system clipboard", #paths))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
assert(jid > 0, "Failed to start job")
|
||||||
|
if stdin then
|
||||||
|
vim.api.nvim_chan_send(jid, stdin)
|
||||||
|
vim.fn.chanclose(jid, "stdin")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param lines string[]
|
||||||
|
---@return string[]
|
||||||
|
local function handle_paste_output_mac(lines)
|
||||||
|
local ret = {}
|
||||||
|
for _, line in ipairs(lines) do
|
||||||
|
if not line:match("^%s*$") then
|
||||||
|
table.insert(ret, line)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param lines string[]
|
||||||
|
---@return string[]
|
||||||
|
local function handle_paste_output_linux(lines)
|
||||||
|
local ret = {}
|
||||||
|
for _, line in ipairs(lines) do
|
||||||
|
local path = line:match("^file://(.+)$")
|
||||||
|
if path then
|
||||||
|
table.insert(ret, util.url_unescape(path))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param delete_original? boolean Delete the source file after pasting
|
||||||
|
M.paste_from_system_clipboard = function(delete_original)
|
||||||
|
local dir = oil.get_current_dir()
|
||||||
|
if not dir then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local cmd = {}
|
||||||
|
local handle_paste_output
|
||||||
|
if fs.is_mac then
|
||||||
|
cmd = {
|
||||||
|
"osascript",
|
||||||
|
"-e",
|
||||||
|
"on run",
|
||||||
|
"-e",
|
||||||
|
"POSIX path of (the clipboard as «class furl»)",
|
||||||
|
"-e",
|
||||||
|
"end run",
|
||||||
|
}
|
||||||
|
handle_paste_output = handle_paste_output_mac
|
||||||
|
elseif fs.is_linux then
|
||||||
|
local xdg_session_type = get_linux_session_type()
|
||||||
|
if xdg_session_type == "x11" then
|
||||||
|
vim.list_extend(cmd, { "xclip", "-o", "-selection", "clipboard" })
|
||||||
|
elseif xdg_session_type == "wayland" then
|
||||||
|
table.insert(cmd, "wl-paste")
|
||||||
|
else
|
||||||
|
vim.notify("System clipboard not supported, check $XDG_SESSION_TYPE", vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if is_linux_desktop_gnome() then
|
||||||
|
vim.list_extend(cmd, { "-t", "x-special/gnome-copied-files" })
|
||||||
|
else
|
||||||
|
vim.list_extend(cmd, { "-t", "text/uri-list" })
|
||||||
|
end
|
||||||
|
handle_paste_output = handle_paste_output_linux
|
||||||
|
else
|
||||||
|
vim.notify("System clipboard not supported on Windows", vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local paths
|
||||||
|
local stderr = ""
|
||||||
|
if vim.fn.executable(cmd[1]) == 0 then
|
||||||
|
vim.notify(string.format("Could not find executable '%s'", cmd[1]), vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local jid = vim.fn.jobstart(cmd, {
|
||||||
|
stdout_buffered = true,
|
||||||
|
stderr_buffered = true,
|
||||||
|
on_stdout = function(j, data)
|
||||||
|
local lines = vim.split(table.concat(data, "\n"), "\r?\n")
|
||||||
|
paths = handle_paste_output(lines)
|
||||||
|
end,
|
||||||
|
on_stderr = function(_, data)
|
||||||
|
stderr = table.concat(data, "\n")
|
||||||
|
end,
|
||||||
|
on_exit = function(j, exit_code)
|
||||||
|
if exit_code ~= 0 or not paths then
|
||||||
|
vim.notify(
|
||||||
|
string.format("Error pasting from system clipboard: %s", stderr),
|
||||||
|
vim.log.levels.ERROR
|
||||||
|
)
|
||||||
|
elseif #paths == 0 then
|
||||||
|
vim.notify("No valid files found in system clipboard", vim.log.levels.WARN)
|
||||||
|
else
|
||||||
|
paste_paths(paths, delete_original)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
assert(jid > 0, "Failed to start job")
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
|
|
@ -12,13 +12,13 @@ local all_columns = {}
|
||||||
---@alias oil.ColumnSpec string|{[1]: string, [string]: any}
|
---@alias oil.ColumnSpec string|{[1]: string, [string]: any}
|
||||||
|
|
||||||
---@class (exact) oil.ColumnDefinition
|
---@class (exact) oil.ColumnDefinition
|
||||||
---@field render fun(entry: oil.InternalEntry, conf: nil|table): nil|oil.TextChunk
|
---@field render fun(entry: oil.InternalEntry, conf: nil|table, bufnr: integer): nil|oil.TextChunk
|
||||||
---@field parse fun(line: string, conf: nil|table): nil|string, nil|string
|
---@field parse fun(line: string, conf: nil|table): nil|string, nil|string
|
||||||
---@field meta_fields? table<string, fun(parent_url: string, entry: oil.InternalEntry, cb: fun(err: nil|string))>
|
|
||||||
---@field compare? fun(entry: oil.InternalEntry, parsed_value: any): boolean
|
---@field compare? fun(entry: oil.InternalEntry, parsed_value: any): boolean
|
||||||
---@field render_action? fun(action: oil.ChangeAction): string
|
---@field render_action? fun(action: oil.ChangeAction): string
|
||||||
---@field perform_action? fun(action: oil.ChangeAction, callback: fun(err: nil|string))
|
---@field perform_action? fun(action: oil.ChangeAction, callback: fun(err: nil|string))
|
||||||
---@field get_sort_value? fun(entry: oil.InternalEntry): number|string
|
---@field get_sort_value? fun(entry: oil.InternalEntry): number|string
|
||||||
|
---@field create_sort_value_factory? fun(num_entries: integer): fun(entry: oil.InternalEntry): number|string
|
||||||
|
|
||||||
---@param name string
|
---@param name string
|
||||||
---@param column oil.ColumnDefinition
|
---@param column oil.ColumnDefinition
|
||||||
|
|
@ -53,55 +53,16 @@ M.get_supported_columns = function(adapter_or_scheme)
|
||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param adapter oil.Adapter
|
local EMPTY = { "-", "OilEmpty" }
|
||||||
---@param column_defs table[]
|
|
||||||
---@return fun(parent_url: string, entry: oil.InternalEntry, cb: fun(err: nil|string))
|
|
||||||
M.get_metadata_fetcher = function(adapter, column_defs)
|
|
||||||
local keyfetches = {}
|
|
||||||
local num_keys = 0
|
|
||||||
for _, def in ipairs(column_defs) do
|
|
||||||
local name = util.split_config(def)
|
|
||||||
local column = M.get_column(adapter, name)
|
|
||||||
if column and column.meta_fields then
|
|
||||||
for k, v in pairs(column.meta_fields) do
|
|
||||||
if not keyfetches[k] then
|
|
||||||
keyfetches[k] = v
|
|
||||||
num_keys = num_keys + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if num_keys == 0 then
|
|
||||||
return function(_, _, cb)
|
|
||||||
cb()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return function(parent_url, entry, cb)
|
|
||||||
cb = util.cb_collect(num_keys, cb)
|
|
||||||
local meta = {}
|
|
||||||
entry[FIELD_META] = meta
|
|
||||||
for k, v in pairs(keyfetches) do
|
|
||||||
v(parent_url, entry, function(err, value)
|
|
||||||
if err then
|
|
||||||
cb(err)
|
|
||||||
else
|
|
||||||
meta[k] = value
|
|
||||||
cb()
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local EMPTY = { "-", "Comment" }
|
|
||||||
|
|
||||||
M.EMPTY = EMPTY
|
M.EMPTY = EMPTY
|
||||||
|
|
||||||
---@param adapter oil.Adapter
|
---@param adapter oil.Adapter
|
||||||
---@param col_def oil.ColumnSpec
|
---@param col_def oil.ColumnSpec
|
||||||
---@param entry oil.InternalEntry
|
---@param entry oil.InternalEntry
|
||||||
|
---@param bufnr integer
|
||||||
---@return oil.TextChunk
|
---@return oil.TextChunk
|
||||||
M.render_col = function(adapter, col_def, entry)
|
M.render_col = function(adapter, col_def, entry, bufnr)
|
||||||
local name, conf = util.split_config(col_def)
|
local name, conf = util.split_config(col_def)
|
||||||
local column = M.get_column(adapter, name)
|
local column = M.get_column(adapter, name)
|
||||||
if not column then
|
if not column then
|
||||||
|
|
@ -109,19 +70,7 @@ M.render_col = function(adapter, col_def, entry)
|
||||||
return EMPTY
|
return EMPTY
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Make sure all the required metadata exists before attempting to render
|
local chunk = column.render(entry, conf, bufnr)
|
||||||
if column.meta_fields then
|
|
||||||
local meta = entry[FIELD_META]
|
|
||||||
if not meta then
|
|
||||||
return EMPTY
|
|
||||||
end
|
|
||||||
for k in pairs(column.meta_fields) do
|
|
||||||
if not meta[k] then
|
|
||||||
return EMPTY
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local chunk = column.render(entry, conf)
|
|
||||||
if type(chunk) == "table" then
|
if type(chunk) == "table" then
|
||||||
if chunk[1]:match("^%s*$") then
|
if chunk[1]:match("^%s*$") then
|
||||||
return EMPTY
|
return EMPTY
|
||||||
|
|
@ -149,13 +98,13 @@ end
|
||||||
M.parse_col = function(adapter, line, col_def)
|
M.parse_col = function(adapter, line, col_def)
|
||||||
local name, conf = util.split_config(col_def)
|
local name, conf = util.split_config(col_def)
|
||||||
-- If rendering failed, there will just be a "-"
|
-- If rendering failed, there will just be a "-"
|
||||||
local empty_col, rem = line:match("^(-%s+)(.*)$")
|
local empty_col, rem = line:match("^%s*(-%s+)(.*)$")
|
||||||
if empty_col then
|
if empty_col then
|
||||||
return nil, rem
|
return nil, rem
|
||||||
end
|
end
|
||||||
local column = M.get_column(adapter, name)
|
local column = M.get_column(adapter, name)
|
||||||
if column then
|
if column then
|
||||||
return column.parse(line, conf)
|
return column.parse(line:gsub("^%s+", ""), conf)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -251,7 +200,7 @@ local function is_entry_directory(entry)
|
||||||
return true
|
return true
|
||||||
elseif type == "link" then
|
elseif type == "link" then
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
return meta and meta.link_stat and meta.link_stat.type == "directory"
|
return (meta and meta.link_stat and meta.link_stat.type == "directory") == true
|
||||||
else
|
else
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
@ -279,8 +228,8 @@ M.register("type", {
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
local function pad_number(int)
|
local function adjust_number(int)
|
||||||
return string.format("%012d", int)
|
return string.format("%03d%s", #int, int)
|
||||||
end
|
end
|
||||||
|
|
||||||
M.register("name", {
|
M.register("name", {
|
||||||
|
|
@ -292,18 +241,33 @@ M.register("name", {
|
||||||
error("Do not use the name column. It is for sorting only")
|
error("Do not use the name column. It is for sorting only")
|
||||||
end,
|
end,
|
||||||
|
|
||||||
get_sort_value = function(entry)
|
create_sort_value_factory = function(num_entries)
|
||||||
local sort_value = entry[FIELD_NAME]
|
if
|
||||||
|
config.view_options.natural_order == false
|
||||||
if config.view_options.natural_order then
|
or (config.view_options.natural_order == "fast" and num_entries > 5000)
|
||||||
sort_value = sort_value:gsub("%d+", pad_number)
|
then
|
||||||
end
|
|
||||||
|
|
||||||
if config.view_options.case_insensitive then
|
if config.view_options.case_insensitive then
|
||||||
sort_value = sort_value:lower()
|
return function(entry)
|
||||||
|
return entry[FIELD_NAME]:lower()
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return function(entry)
|
||||||
|
return entry[FIELD_NAME]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local memo = {}
|
||||||
|
return function(entry)
|
||||||
|
if memo[entry] == nil then
|
||||||
|
local name = entry[FIELD_NAME]:gsub("0*(%d+)", adjust_number)
|
||||||
|
if config.view_options.case_insensitive then
|
||||||
|
name = name:lower()
|
||||||
|
end
|
||||||
|
memo[entry] = name
|
||||||
|
end
|
||||||
|
return memo[entry]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return sort_value
|
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
--stylua: ignore
|
|
||||||
|
|
||||||
local default_config = {
|
local default_config = {
|
||||||
-- Oil will take over directory buffers (e.g. `vim .` or `:e src/`)
|
-- Oil will take over directory buffers (e.g. `vim .` or `:e src/`)
|
||||||
-- Set to false if you want some other plugin (e.g. netrw) to open when you edit directories.
|
-- Set to false if you want some other plugin (e.g. netrw) to open when you edit directories.
|
||||||
|
|
@ -60,22 +58,22 @@ local default_config = {
|
||||||
-- Set to `false` to remove a keymap
|
-- Set to `false` to remove a keymap
|
||||||
-- See :help oil-actions for a list of all available actions
|
-- See :help oil-actions for a list of all available actions
|
||||||
keymaps = {
|
keymaps = {
|
||||||
["g?"] = "actions.show_help",
|
["g?"] = { "actions.show_help", mode = "n" },
|
||||||
["<CR>"] = "actions.select",
|
["<CR>"] = "actions.select",
|
||||||
["<C-s>"] = { "actions.select", opts = { vertical = true }, desc = "Open the entry in a vertical split" },
|
["<C-s>"] = { "actions.select", opts = { vertical = true } },
|
||||||
["<C-h>"] = { "actions.select", opts = { horizontal = true }, desc = "Open the entry in a horizontal split" },
|
["<C-h>"] = { "actions.select", opts = { horizontal = true } },
|
||||||
["<C-t>"] = { "actions.select", opts = { tab = true }, desc = "Open the entry in new tab" },
|
["<C-t>"] = { "actions.select", opts = { tab = true } },
|
||||||
["<C-p>"] = "actions.preview",
|
["<C-p>"] = "actions.preview",
|
||||||
["<C-c>"] = "actions.close",
|
["<C-c>"] = { "actions.close", mode = "n" },
|
||||||
["<C-l>"] = "actions.refresh",
|
["<C-l>"] = "actions.refresh",
|
||||||
["-"] = "actions.parent",
|
["-"] = { "actions.parent", mode = "n" },
|
||||||
["_"] = "actions.open_cwd",
|
["_"] = { "actions.open_cwd", mode = "n" },
|
||||||
["`"] = "actions.cd",
|
["`"] = { "actions.cd", mode = "n" },
|
||||||
["~"] = { "actions.cd", opts = { scope = "tab" }, desc = ":tcd to the current oil directory", mode = "n" },
|
["g~"] = { "actions.cd", opts = { scope = "tab" }, mode = "n" },
|
||||||
["gs"] = "actions.change_sort",
|
["gs"] = { "actions.change_sort", mode = "n" },
|
||||||
["gx"] = "actions.open_external",
|
["gx"] = "actions.open_external",
|
||||||
["g."] = "actions.toggle_hidden",
|
["g."] = { "actions.toggle_hidden", mode = "n" },
|
||||||
["g\\"] = "actions.toggle_trash",
|
["g\\"] = { "actions.toggle_trash", mode = "n" },
|
||||||
},
|
},
|
||||||
-- Set to false to disable all of the above keymaps
|
-- Set to false to disable all of the above keymaps
|
||||||
use_default_keymaps = true,
|
use_default_keymaps = true,
|
||||||
|
|
@ -84,15 +82,16 @@ local default_config = {
|
||||||
show_hidden = false,
|
show_hidden = false,
|
||||||
-- This function defines what is considered a "hidden" file
|
-- This function defines what is considered a "hidden" file
|
||||||
is_hidden_file = function(name, bufnr)
|
is_hidden_file = function(name, bufnr)
|
||||||
return vim.startswith(name, ".")
|
local m = name:match("^%.")
|
||||||
|
return m ~= nil
|
||||||
end,
|
end,
|
||||||
-- This function defines what will never be shown, even when `show_hidden` is set
|
-- This function defines what will never be shown, even when `show_hidden` is set
|
||||||
is_always_hidden = function(name, bufnr)
|
is_always_hidden = function(name, bufnr)
|
||||||
return false
|
return false
|
||||||
end,
|
end,
|
||||||
-- Sort file names in a more intuitive order for humans. Is less performant,
|
-- Sort file names with numbers in a more intuitive order for humans.
|
||||||
-- so you may want to set to false if you work with large directories.
|
-- Can be "fast", true, or false. "fast" will turn it off for large directories.
|
||||||
natural_order = true,
|
natural_order = "fast",
|
||||||
-- Sort file and directory names case insensitive
|
-- Sort file and directory names case insensitive
|
||||||
case_insensitive = false,
|
case_insensitive = false,
|
||||||
sort = {
|
sort = {
|
||||||
|
|
@ -101,9 +100,15 @@ local default_config = {
|
||||||
{ "type", "asc" },
|
{ "type", "asc" },
|
||||||
{ "name", "asc" },
|
{ "name", "asc" },
|
||||||
},
|
},
|
||||||
|
-- Customize the highlight group for the file name
|
||||||
|
highlight_filename = function(entry, is_hidden, is_link_target, is_link_orphan)
|
||||||
|
return nil
|
||||||
|
end,
|
||||||
},
|
},
|
||||||
-- Extra arguments to pass to SCP when moving/copying files over SSH
|
-- Extra arguments to pass to SCP when moving/copying files over SSH
|
||||||
extra_scp_args = {},
|
extra_scp_args = {},
|
||||||
|
-- Extra arguments to pass to aws s3 when creating/deleting/moving/copying files using aws s3
|
||||||
|
extra_s3_args = {},
|
||||||
-- EXPERIMENTAL support for performing file operations with git
|
-- EXPERIMENTAL support for performing file operations with git
|
||||||
git = {
|
git = {
|
||||||
-- Return true to automatically git add/mv/rm files
|
-- Return true to automatically git add/mv/rm files
|
||||||
|
|
@ -121,9 +126,10 @@ local default_config = {
|
||||||
float = {
|
float = {
|
||||||
-- Padding around the floating window
|
-- Padding around the floating window
|
||||||
padding = 2,
|
padding = 2,
|
||||||
|
-- max_width and max_height can be integers or a float between 0 and 1 (e.g. 0.4 for 40%)
|
||||||
max_width = 0,
|
max_width = 0,
|
||||||
max_height = 0,
|
max_height = 0,
|
||||||
border = "rounded",
|
border = nil,
|
||||||
win_options = {
|
win_options = {
|
||||||
winblend = 0,
|
winblend = 0,
|
||||||
},
|
},
|
||||||
|
|
@ -141,6 +147,14 @@ local default_config = {
|
||||||
preview_win = {
|
preview_win = {
|
||||||
-- Whether the preview window is automatically updated when the cursor is moved
|
-- Whether the preview window is automatically updated when the cursor is moved
|
||||||
update_on_cursor_moved = true,
|
update_on_cursor_moved = true,
|
||||||
|
-- How to open the preview window "load"|"scratch"|"fast_scratch"
|
||||||
|
preview_method = "fast_scratch",
|
||||||
|
-- A function that returns true to disable preview on a file e.g. to avoid lag
|
||||||
|
disable_preview = function(filename)
|
||||||
|
return false
|
||||||
|
end,
|
||||||
|
-- Window-local options to use for preview window buffers
|
||||||
|
win_options = {},
|
||||||
},
|
},
|
||||||
-- Configuration for the floating action confirmation window
|
-- Configuration for the floating action confirmation window
|
||||||
confirmation = {
|
confirmation = {
|
||||||
|
|
@ -160,7 +174,7 @@ local default_config = {
|
||||||
min_height = { 5, 0.1 },
|
min_height = { 5, 0.1 },
|
||||||
-- optionally define an integer/float for the exact height of the preview window
|
-- optionally define an integer/float for the exact height of the preview window
|
||||||
height = nil,
|
height = nil,
|
||||||
border = "rounded",
|
border = nil,
|
||||||
win_options = {
|
win_options = {
|
||||||
winblend = 0,
|
winblend = 0,
|
||||||
},
|
},
|
||||||
|
|
@ -173,7 +187,7 @@ local default_config = {
|
||||||
max_height = { 10, 0.9 },
|
max_height = { 10, 0.9 },
|
||||||
min_height = { 5, 0.1 },
|
min_height = { 5, 0.1 },
|
||||||
height = nil,
|
height = nil,
|
||||||
border = "rounded",
|
border = nil,
|
||||||
minimized_border = "none",
|
minimized_border = "none",
|
||||||
win_options = {
|
win_options = {
|
||||||
winblend = 0,
|
winblend = 0,
|
||||||
|
|
@ -181,28 +195,35 @@ local default_config = {
|
||||||
},
|
},
|
||||||
-- Configuration for the floating SSH window
|
-- Configuration for the floating SSH window
|
||||||
ssh = {
|
ssh = {
|
||||||
border = "rounded",
|
border = nil,
|
||||||
},
|
},
|
||||||
-- Configuration for the floating keymaps help window
|
-- Configuration for the floating keymaps help window
|
||||||
keymaps_help = {
|
keymaps_help = {
|
||||||
border = "rounded",
|
border = nil,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
-- The adapter API hasn't really stabilized yet. We're not ready to advertise or encourage people to
|
-- The adapter API hasn't really stabilized yet. We're not ready to advertise or encourage people to
|
||||||
-- write their own adapters, and so there's no real reason to edit these config options. For that
|
-- write their own adapters, and so there's no real reason to edit these config options. For that
|
||||||
-- reason, I'm taking them out of the section above so they won't show up in the autogen docs.
|
-- reason, I'm taking them out of the section above so they won't show up in the autogen docs.
|
||||||
|
|
||||||
|
-- not "oil-s3://" on older neovim versions, since it doesn't open buffers correctly with a number
|
||||||
|
-- in the name
|
||||||
|
local oil_s3_string = vim.fn.has("nvim-0.12") == 1 and "oil-s3://" or "oil-sss://"
|
||||||
default_config.adapters = {
|
default_config.adapters = {
|
||||||
["oil://"] = "files",
|
["oil://"] = "files",
|
||||||
["oil-ssh://"] = "ssh",
|
["oil-ssh://"] = "ssh",
|
||||||
|
[oil_s3_string] = "s3",
|
||||||
["oil-trash://"] = "trash",
|
["oil-trash://"] = "trash",
|
||||||
}
|
}
|
||||||
default_config.adapter_aliases = {}
|
default_config.adapter_aliases = {}
|
||||||
|
-- We want the function in the default config for documentation generation, but if we nil it out
|
||||||
|
-- here we can get some performance wins
|
||||||
|
default_config.view_options.highlight_filename = nil
|
||||||
|
|
||||||
---@class oil.Config
|
---@class oil.Config
|
||||||
---@field adapters table<string, string> Hidden from SetupOpts
|
---@field adapters table<string, string> Hidden from SetupOpts
|
||||||
---@field adapter_aliases table<string, string> Hidden from SetupOpts
|
---@field adapter_aliases table<string, string> Hidden from SetupOpts
|
||||||
---@field trash_command? string Deprecated option that we should clean up soon
|
|
||||||
---@field silence_scp_warning? boolean Undocumented option
|
---@field silence_scp_warning? boolean Undocumented option
|
||||||
---@field default_file_explorer boolean
|
---@field default_file_explorer boolean
|
||||||
---@field columns oil.ColumnSpec[]
|
---@field columns oil.ColumnSpec[]
|
||||||
|
|
@ -219,6 +240,7 @@ default_config.adapter_aliases = {}
|
||||||
---@field use_default_keymaps boolean
|
---@field use_default_keymaps boolean
|
||||||
---@field view_options oil.ViewOptions
|
---@field view_options oil.ViewOptions
|
||||||
---@field extra_scp_args string[]
|
---@field extra_scp_args string[]
|
||||||
|
---@field extra_s3_args string[]
|
||||||
---@field git oil.GitOptions
|
---@field git oil.GitOptions
|
||||||
---@field float oil.FloatWindowConfig
|
---@field float oil.FloatWindowConfig
|
||||||
---@field preview_win oil.PreviewWindowConfig
|
---@field preview_win oil.PreviewWindowConfig
|
||||||
|
|
@ -247,6 +269,7 @@ local M = {}
|
||||||
---@field use_default_keymaps? boolean Set to false to disable all of the above keymaps
|
---@field use_default_keymaps? boolean Set to false to disable all of the above keymaps
|
||||||
---@field view_options? oil.SetupViewOptions Configure which files are shown and how they are shown.
|
---@field view_options? oil.SetupViewOptions Configure which files are shown and how they are shown.
|
||||||
---@field extra_scp_args? string[] Extra arguments to pass to SCP when moving/copying files over SSH
|
---@field extra_scp_args? string[] Extra arguments to pass to SCP when moving/copying files over SSH
|
||||||
|
---@field extra_s3_args? string[] Extra arguments to pass to aws s3 when moving/copying files using aws s3
|
||||||
---@field git? oil.SetupGitOptions EXPERIMENTAL support for performing file operations with git
|
---@field git? oil.SetupGitOptions EXPERIMENTAL support for performing file operations with git
|
||||||
---@field float? oil.SetupFloatWindowConfig Configuration for the floating window in oil.open_float
|
---@field float? oil.SetupFloatWindowConfig Configuration for the floating window in oil.open_float
|
||||||
---@field preview_win? oil.SetupPreviewWindowConfig Configuration for the file preview window
|
---@field preview_win? oil.SetupPreviewWindowConfig Configuration for the file preview window
|
||||||
|
|
@ -269,17 +292,19 @@ local M = {}
|
||||||
---@field show_hidden boolean
|
---@field show_hidden boolean
|
||||||
---@field is_hidden_file fun(name: string, bufnr: integer): boolean
|
---@field is_hidden_file fun(name: string, bufnr: integer): boolean
|
||||||
---@field is_always_hidden fun(name: string, bufnr: integer): boolean
|
---@field is_always_hidden fun(name: string, bufnr: integer): boolean
|
||||||
---@field natural_order boolean
|
---@field natural_order boolean|"fast"
|
||||||
---@field case_insensitive boolean
|
---@field case_insensitive boolean
|
||||||
---@field sort oil.SortSpec[]
|
---@field sort oil.SortSpec[]
|
||||||
|
---@field highlight_filename? fun(entry: oil.Entry, is_hidden: boolean, is_link_target: boolean, is_link_orphan: boolean, bufnr: integer): string|nil
|
||||||
|
|
||||||
---@class (exact) oil.SetupViewOptions
|
---@class (exact) oil.SetupViewOptions
|
||||||
---@field show_hidden? boolean Show files and directories that start with "."
|
---@field show_hidden? boolean Show files and directories that start with "."
|
||||||
---@field is_hidden_file? fun(name: string, bufnr: integer): boolean This function defines what is considered a "hidden" file
|
---@field is_hidden_file? fun(name: string, bufnr: integer): boolean This function defines what is considered a "hidden" file
|
||||||
---@field is_always_hidden? fun(name: string, bufnr: integer): boolean This function defines what will never be shown, even when `show_hidden` is set
|
---@field is_always_hidden? fun(name: string, bufnr: integer): boolean This function defines what will never be shown, even when `show_hidden` is set
|
||||||
---@field natural_order? boolean Sort file names in a more intuitive order for humans. Is less performant, so you may want to set to false if you work with large directories.
|
---@field natural_order? boolean|"fast" Sort file names with numbers in a more intuitive order for humans. Can be slow for large directories.
|
||||||
---@field case_insensitive? boolean Sort file and directory names case insensitive
|
---@field case_insensitive? boolean Sort file and directory names case insensitive
|
||||||
---@field sort? oil.SortSpec[] Sort order for the file list
|
---@field sort? oil.SortSpec[] Sort order for the file list
|
||||||
|
---@field highlight_filename? fun(entry: oil.Entry, is_hidden: boolean, is_link_target: boolean, is_link_orphan: boolean): string|nil Customize the highlight group for the file name
|
||||||
|
|
||||||
---@class (exact) oil.SortSpec
|
---@class (exact) oil.SortSpec
|
||||||
---@field [1] string
|
---@field [1] string
|
||||||
|
|
@ -321,13 +346,24 @@ local M = {}
|
||||||
---@field border? string|string[] Window border
|
---@field border? string|string[] Window border
|
||||||
---@field win_options? table<string, any>
|
---@field win_options? table<string, any>
|
||||||
|
|
||||||
|
---@alias oil.PreviewMethod
|
||||||
|
---| '"load"' # Load the previewed file into a buffer
|
||||||
|
---| '"scratch"' # Put the text into a scratch buffer to avoid LSP attaching
|
||||||
|
---| '"fast_scratch"' # Put only the visible text into a scratch buffer
|
||||||
|
|
||||||
---@class (exact) oil.PreviewWindowConfig
|
---@class (exact) oil.PreviewWindowConfig
|
||||||
---@field update_on_cursor_moved boolean
|
---@field update_on_cursor_moved boolean
|
||||||
|
---@field preview_method oil.PreviewMethod
|
||||||
|
---@field disable_preview fun(filename: string): boolean
|
||||||
|
---@field win_options table<string, any>
|
||||||
|
|
||||||
---@class (exact) oil.ConfirmationWindowConfig : oil.WindowConfig
|
---@class (exact) oil.ConfirmationWindowConfig : oil.WindowConfig
|
||||||
|
|
||||||
---@class (exact) oil.SetupPreviewWindowConfig
|
---@class (exact) oil.SetupPreviewWindowConfig
|
||||||
---@field update_on_cursor_moved? boolean Whether the preview window is automatically updated when the cursor is moved
|
---@field update_on_cursor_moved? boolean Whether the preview window is automatically updated when the cursor is moved
|
||||||
|
---@field disable_preview? fun(filename: string): boolean A function that returns true to disable preview on a file e.g. to avoid lag
|
||||||
|
---@field preview_method? oil.PreviewMethod How to open the preview window
|
||||||
|
---@field win_options? table<string, any> Window-local options to use for preview window buffers
|
||||||
|
|
||||||
---@class (exact) oil.SetupConfirmationWindowConfig : oil.SetupWindowConfig
|
---@class (exact) oil.SetupConfirmationWindowConfig : oil.SetupWindowConfig
|
||||||
|
|
||||||
|
|
@ -377,6 +413,17 @@ M.setup = function(opts)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Backwards compatibility for old versions that don't support winborder
|
||||||
|
if vim.fn.has("nvim-0.11") == 0 then
|
||||||
|
new_conf = vim.tbl_deep_extend("keep", new_conf, {
|
||||||
|
float = { border = "rounded" },
|
||||||
|
confirmation = { border = "rounded" },
|
||||||
|
progress = { border = "rounded" },
|
||||||
|
ssh = { border = "rounded" },
|
||||||
|
keymaps_help = { border = "rounded" },
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
-- Backwards compatibility. We renamed the 'preview' window config to be called 'confirmation'.
|
-- Backwards compatibility. We renamed the 'preview' window config to be called 'confirmation'.
|
||||||
if opts.preview and not opts.confirmation then
|
if opts.preview and not opts.confirmation then
|
||||||
new_conf.confirmation = vim.tbl_deep_extend("keep", opts.preview, default_config.confirmation)
|
new_conf.confirmation = vim.tbl_deep_extend("keep", opts.preview, default_config.confirmation)
|
||||||
|
|
@ -429,10 +476,6 @@ M.get_adapter_by_scheme = function(scheme)
|
||||||
if adapter == nil then
|
if adapter == nil then
|
||||||
local name = M.adapters[scheme]
|
local name = M.adapters[scheme]
|
||||||
if not name then
|
if not name then
|
||||||
vim.notify(
|
|
||||||
string.format("Could not find oil adapter for scheme '%s'", scheme),
|
|
||||||
vim.log.levels.ERROR
|
|
||||||
)
|
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
local ok
|
local ok
|
||||||
|
|
@ -443,7 +486,6 @@ M.get_adapter_by_scheme = function(scheme)
|
||||||
else
|
else
|
||||||
M._adapter_by_scheme[scheme] = false
|
M._adapter_by_scheme[scheme] = false
|
||||||
adapter = false
|
adapter = false
|
||||||
vim.notify(string.format("Could not find oil adapter '%s'", name), vim.log.levels.ERROR)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if adapter then
|
if adapter then
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ local M = {}
|
||||||
|
|
||||||
---Store entries as a list-like table for maximum space efficiency and retrieval speed.
|
---Store entries as a list-like table for maximum space efficiency and retrieval speed.
|
||||||
---We use the constants below to index into the table.
|
---We use the constants below to index into the table.
|
||||||
---@alias oil.InternalEntry any[]
|
---@alias oil.InternalEntry {[1]: integer, [2]: string, [3]: oil.EntryType, [4]: nil|table}
|
||||||
|
|
||||||
-- Indexes into oil.InternalEntry
|
-- Indexes into oil.InternalEntry
|
||||||
M.FIELD_ID = 1
|
M.FIELD_ID = 1
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
local log = require("oil.log")
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local uv = vim.uv or vim.loop
|
local uv = vim.uv or vim.loop
|
||||||
|
|
@ -217,7 +218,7 @@ M.recursive_delete = function(entry_type, path, cb)
|
||||||
local waiting = #entries
|
local waiting = #entries
|
||||||
local complete
|
local complete
|
||||||
complete = function(err2)
|
complete = function(err2)
|
||||||
if err then
|
if err2 then
|
||||||
complete = function() end
|
complete = function() end
|
||||||
return inner_cb(err2)
|
return inner_cb(err2)
|
||||||
end
|
end
|
||||||
|
|
@ -245,6 +246,37 @@ M.recursive_delete = function(entry_type, path, cb)
|
||||||
end, 10000)
|
end, 10000)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---Move the undofile for the file at src_path to dest_path
|
||||||
|
---@param src_path string
|
||||||
|
---@param dest_path string
|
||||||
|
---@param copy boolean
|
||||||
|
local move_undofile = vim.schedule_wrap(function(src_path, dest_path, copy)
|
||||||
|
local undofile = vim.fn.undofile(src_path)
|
||||||
|
uv.fs_stat(
|
||||||
|
undofile,
|
||||||
|
vim.schedule_wrap(function(stat_err)
|
||||||
|
if stat_err then
|
||||||
|
-- undofile doesn't exist
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local dest_undofile = vim.fn.undofile(dest_path)
|
||||||
|
if copy then
|
||||||
|
uv.fs_copyfile(src_path, dest_path, function(err)
|
||||||
|
if err then
|
||||||
|
log.warn("Error copying undofile %s: %s", undofile, err)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
uv.fs_rename(undofile, dest_undofile, function(err)
|
||||||
|
if err then
|
||||||
|
log.warn("Error moving undofile %s: %s", undofile, err)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
|
||||||
---@param entry_type oil.EntryType
|
---@param entry_type oil.EntryType
|
||||||
---@param src_path string
|
---@param src_path string
|
||||||
---@param dest_path string
|
---@param dest_path string
|
||||||
|
|
@ -262,6 +294,7 @@ M.recursive_copy = function(entry_type, src_path, dest_path, cb)
|
||||||
end
|
end
|
||||||
if entry_type ~= "directory" then
|
if entry_type ~= "directory" then
|
||||||
uv.fs_copyfile(src_path, dest_path, { excl = true }, cb)
|
uv.fs_copyfile(src_path, dest_path, { excl = true }, cb)
|
||||||
|
move_undofile(src_path, dest_path, true)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
uv.fs_stat(src_path, function(stat_err, src_stat)
|
uv.fs_stat(src_path, function(stat_err, src_stat)
|
||||||
|
|
@ -287,7 +320,7 @@ M.recursive_copy = function(entry_type, src_path, dest_path, cb)
|
||||||
local waiting = #entries
|
local waiting = #entries
|
||||||
local complete
|
local complete
|
||||||
complete = function(err2)
|
complete = function(err2)
|
||||||
if err then
|
if err2 then
|
||||||
complete = function() end
|
complete = function() end
|
||||||
return inner_cb(err2)
|
return inner_cb(err2)
|
||||||
end
|
end
|
||||||
|
|
@ -333,6 +366,9 @@ M.recursive_move = function(entry_type, src_path, dest_path, cb)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
else
|
else
|
||||||
|
if entry_type ~= "directory" then
|
||||||
|
move_undofile(src_path, dest_path, false)
|
||||||
|
end
|
||||||
cb()
|
cb()
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
|
||||||
234
lua/oil/init.lua
234
lua/oil/init.lua
|
|
@ -7,7 +7,7 @@ local M = {}
|
||||||
---@field parsed_name nil|string
|
---@field parsed_name nil|string
|
||||||
---@field meta nil|table
|
---@field meta nil|table
|
||||||
|
|
||||||
---@alias oil.EntryType "file"|"directory"|"socket"|"link"|"fifo"
|
---@alias oil.EntryType uv.aliases.fs_types
|
||||||
---@alias oil.HlRange { [1]: string, [2]: integer, [3]: integer } A tuple of highlight group name, col_start, col_end
|
---@alias oil.HlRange { [1]: string, [2]: integer, [3]: integer } A tuple of highlight group name, col_start, col_end
|
||||||
---@alias oil.HlTuple { [1]: string, [2]: string } A tuple of text, highlight group
|
---@alias oil.HlTuple { [1]: string, [2]: string } A tuple of text, highlight group
|
||||||
---@alias oil.HlRangeTuple { [1]: string, [2]: oil.HlRange[] } A tuple of text, internal highlights
|
---@alias oil.HlRangeTuple { [1]: string, [2]: oil.HlRange[] } A tuple of text, internal highlights
|
||||||
|
|
@ -222,7 +222,7 @@ M.get_buffer_parent_url = function(bufname, use_oil_parent)
|
||||||
if not use_oil_parent then
|
if not use_oil_parent then
|
||||||
return bufname
|
return bufname
|
||||||
end
|
end
|
||||||
local adapter = config.get_adapter_by_scheme(scheme)
|
local adapter = assert(config.get_adapter_by_scheme(scheme))
|
||||||
local parent_url
|
local parent_url
|
||||||
if adapter and adapter.get_parent then
|
if adapter and adapter.get_parent then
|
||||||
local adapter_scheme = config.adapter_to_scheme[adapter.name]
|
local adapter_scheme = config.adapter_to_scheme[adapter.name]
|
||||||
|
|
@ -239,18 +239,21 @@ M.get_buffer_parent_url = function(bufname, use_oil_parent)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@class (exact) oil.OpenOpts
|
||||||
|
---@field preview? oil.OpenPreviewOpts When present, open the preview window after opening oil
|
||||||
|
|
||||||
---Open oil browser in a floating window
|
---Open oil browser in a floating window
|
||||||
---@param dir nil|string When nil, open the parent of the current buffer, or the cwd if current buffer is not a file
|
---@param dir? string When nil, open the parent of the current buffer, or the cwd if current buffer is not a file
|
||||||
M.open_float = function(dir)
|
---@param opts? oil.OpenOpts
|
||||||
|
---@param cb? fun() Called after the oil buffer is ready
|
||||||
|
M.open_float = function(dir, opts, cb)
|
||||||
|
opts = opts or {}
|
||||||
local config = require("oil.config")
|
local config = require("oil.config")
|
||||||
local layout = require("oil.layout")
|
local layout = require("oil.layout")
|
||||||
local util = require("oil.util")
|
local util = require("oil.util")
|
||||||
local view = require("oil.view")
|
local view = require("oil.view")
|
||||||
|
|
||||||
local parent_url, basename = M.get_url_for_path(dir)
|
local parent_url, basename = M.get_url_for_path(dir)
|
||||||
if not parent_url then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
if basename then
|
if basename then
|
||||||
view.set_last_cursor(parent_url, basename)
|
view.set_last_cursor(parent_url, basename)
|
||||||
end
|
end
|
||||||
|
|
@ -324,6 +327,14 @@ M.open_float = function(dir)
|
||||||
vim.api.nvim_set_option_value("buflisted", config.buf_options.buflisted, { buf = 0 })
|
vim.api.nvim_set_option_value("buflisted", config.buf_options.buflisted, { buf = 0 })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
util.run_after_load(0, function()
|
||||||
|
if opts.preview then
|
||||||
|
M.open_preview(opts.preview, cb)
|
||||||
|
elseif cb then
|
||||||
|
cb()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
if vim.fn.has("nvim-0.9") == 0 then
|
if vim.fn.has("nvim-0.9") == 0 then
|
||||||
util.add_title_to_win(winid)
|
util.add_title_to_win(winid)
|
||||||
end
|
end
|
||||||
|
|
@ -331,11 +342,16 @@ end
|
||||||
|
|
||||||
---Open oil browser in a floating window, or close it if open
|
---Open oil browser in a floating window, or close it if open
|
||||||
---@param dir nil|string When nil, open the parent of the current buffer, or the cwd if current buffer is not a file
|
---@param dir nil|string When nil, open the parent of the current buffer, or the cwd if current buffer is not a file
|
||||||
M.toggle_float = function(dir)
|
---@param opts? oil.OpenOpts
|
||||||
|
---@param cb? fun() Called after the oil buffer is ready
|
||||||
|
M.toggle_float = function(dir, opts, cb)
|
||||||
if vim.w.is_oil_win then
|
if vim.w.is_oil_win then
|
||||||
M.close()
|
M.close()
|
||||||
|
if cb then
|
||||||
|
cb()
|
||||||
|
end
|
||||||
else
|
else
|
||||||
M.open_float(dir)
|
M.open_float(dir, opts, cb)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -357,15 +373,15 @@ local function update_preview_window(oil_bufnr)
|
||||||
end
|
end
|
||||||
|
|
||||||
---Open oil browser for a directory
|
---Open oil browser for a directory
|
||||||
---@param dir nil|string When nil, open the parent of the current buffer, or the cwd if current buffer is not a file
|
---@param dir? string When nil, open the parent of the current buffer, or the cwd if current buffer is not a file
|
||||||
M.open = function(dir)
|
---@param opts? oil.OpenOpts
|
||||||
|
---@param cb? fun() Called after the oil buffer is ready
|
||||||
|
M.open = function(dir, opts, cb)
|
||||||
|
opts = opts or {}
|
||||||
local config = require("oil.config")
|
local config = require("oil.config")
|
||||||
local util = require("oil.util")
|
local util = require("oil.util")
|
||||||
local view = require("oil.view")
|
local view = require("oil.view")
|
||||||
local parent_url, basename = M.get_url_for_path(dir)
|
local parent_url, basename = M.get_url_for_path(dir)
|
||||||
if not parent_url then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
if basename then
|
if basename then
|
||||||
view.set_last_cursor(parent_url, basename)
|
view.set_last_cursor(parent_url, basename)
|
||||||
end
|
end
|
||||||
|
|
@ -375,12 +391,25 @@ M.open = function(dir)
|
||||||
vim.api.nvim_set_option_value("buflisted", config.buf_options.buflisted, { buf = 0 })
|
vim.api.nvim_set_option_value("buflisted", config.buf_options.buflisted, { buf = 0 })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
util.run_after_load(0, function()
|
||||||
|
if opts.preview then
|
||||||
|
M.open_preview(opts.preview, cb)
|
||||||
|
elseif cb then
|
||||||
|
cb()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
-- If preview window exists, update its content
|
-- If preview window exists, update its content
|
||||||
update_preview_window()
|
update_preview_window()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@class oil.CloseOpts
|
||||||
|
---@field exit_if_last_buf? boolean Exit vim if this oil buffer is the last open buffer
|
||||||
|
|
||||||
---Restore the buffer that was present when oil was opened
|
---Restore the buffer that was present when oil was opened
|
||||||
M.close = function()
|
---@param opts? oil.CloseOpts
|
||||||
|
M.close = function(opts)
|
||||||
|
opts = opts or {}
|
||||||
-- If we're in a floating oil window, close it and try to restore focus to the original window
|
-- If we're in a floating oil window, close it and try to restore focus to the original window
|
||||||
if vim.w.is_oil_win then
|
if vim.w.is_oil_win then
|
||||||
local original_winid = vim.w.oil_original_win
|
local original_winid = vim.w.oil_original_win
|
||||||
|
|
@ -403,10 +432,15 @@ M.close = function()
|
||||||
-- buffer first
|
-- buffer first
|
||||||
local oilbuf = vim.api.nvim_get_current_buf()
|
local oilbuf = vim.api.nvim_get_current_buf()
|
||||||
ok = pcall(vim.cmd.bprev)
|
ok = pcall(vim.cmd.bprev)
|
||||||
|
-- If `bprev` failed, there are no buffers open
|
||||||
if not ok then
|
if not ok then
|
||||||
-- If `bprev` failed, there are no buffers open so we should create a new one with enew
|
-- either exit or create a new blank buffer
|
||||||
|
if opts.exit_if_last_buf then
|
||||||
|
vim.cmd.quit()
|
||||||
|
else
|
||||||
vim.cmd.enew()
|
vim.cmd.enew()
|
||||||
end
|
end
|
||||||
|
end
|
||||||
vim.api.nvim_buf_delete(oilbuf, { force = true })
|
vim.api.nvim_buf_delete(oilbuf, { force = true })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -444,7 +478,7 @@ M.open_preview = function(opts, callback)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local preview_win = util.get_preview_win()
|
local preview_win = util.get_preview_win({ include_not_owned = true })
|
||||||
local prev_win = vim.api.nvim_get_current_win()
|
local prev_win = vim.api.nvim_get_current_win()
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
local bufnr = vim.api.nvim_get_current_buf()
|
||||||
|
|
||||||
|
|
@ -491,6 +525,7 @@ M.open_preview = function(opts, callback)
|
||||||
|
|
||||||
preview_win = vim.api.nvim_open_win(bufnr, true, win_opts)
|
preview_win = vim.api.nvim_open_win(bufnr, true, win_opts)
|
||||||
vim.api.nvim_set_option_value("previewwindow", true, { scope = "local", win = preview_win })
|
vim.api.nvim_set_option_value("previewwindow", true, { scope = "local", win = preview_win })
|
||||||
|
vim.api.nvim_win_set_var(preview_win, "oil_preview", true)
|
||||||
vim.api.nvim_set_current_win(prev_win)
|
vim.api.nvim_set_current_win(prev_win)
|
||||||
elseif vim.fn.has("nvim-0.9") == 1 then
|
elseif vim.fn.has("nvim-0.9") == 1 then
|
||||||
vim.api.nvim_win_set_config(preview_win, { title = entry_title })
|
vim.api.nvim_win_set_config(preview_win, { title = entry_title })
|
||||||
|
|
@ -513,6 +548,8 @@ M.open_preview = function(opts, callback)
|
||||||
end
|
end
|
||||||
|
|
||||||
util.get_edit_path(bufnr, entry, function(normalized_url)
|
util.get_edit_path(bufnr, entry, function(normalized_url)
|
||||||
|
local mc = package.loaded["multicursor-nvim"]
|
||||||
|
local has_multicursors = mc and mc.hasCursors()
|
||||||
local is_visual_mode = util.is_visual_mode()
|
local is_visual_mode = util.is_visual_mode()
|
||||||
if preview_win then
|
if preview_win then
|
||||||
if is_visual_mode then
|
if is_visual_mode then
|
||||||
|
|
@ -522,15 +559,30 @@ M.open_preview = function(opts, callback)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local filebufnr = vim.fn.bufadd(normalized_url)
|
|
||||||
local entry_is_file = not vim.endswith(normalized_url, "/")
|
local entry_is_file = not vim.endswith(normalized_url, "/")
|
||||||
|
local filebufnr
|
||||||
|
if entry_is_file then
|
||||||
|
if config.preview_win.disable_preview(normalized_url) then
|
||||||
|
filebufnr = vim.api.nvim_create_buf(false, true)
|
||||||
|
vim.bo[filebufnr].bufhidden = "wipe"
|
||||||
|
vim.bo[filebufnr].buftype = "nofile"
|
||||||
|
util.render_text(filebufnr, "Preview disabled", { winid = preview_win })
|
||||||
|
elseif
|
||||||
|
config.preview_win.preview_method ~= "load"
|
||||||
|
and not util.file_matches_bufreadcmd(normalized_url)
|
||||||
|
then
|
||||||
|
filebufnr =
|
||||||
|
util.read_file_to_scratch_buffer(normalized_url, config.preview_win.preview_method)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- If we're previewing a file that hasn't been opened yet, make sure it gets deleted after
|
if not filebufnr then
|
||||||
-- we close the window
|
filebufnr = vim.fn.bufadd(normalized_url)
|
||||||
if entry_is_file and vim.fn.bufloaded(filebufnr) == 0 then
|
if entry_is_file and vim.fn.bufloaded(filebufnr) == 0 then
|
||||||
vim.bo[filebufnr].bufhidden = "wipe"
|
vim.bo[filebufnr].bufhidden = "wipe"
|
||||||
vim.b[filebufnr].oil_preview_buffer = true
|
vim.b[filebufnr].oil_preview_buffer = true
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
---@diagnostic disable-next-line: param-type-mismatch
|
---@diagnostic disable-next-line: param-type-mismatch
|
||||||
local ok, err = pcall(vim.cmd, {
|
local ok, err = pcall(vim.cmd, {
|
||||||
|
|
@ -543,10 +595,23 @@ M.open_preview = function(opts, callback)
|
||||||
vim.api.nvim_echo({ { err, "Error" } }, true, {})
|
vim.api.nvim_echo({ { err, "Error" } }, true, {})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- If we called open_preview during an autocmd, then the edit command may not trigger the
|
||||||
|
-- BufReadCmd to load the buffer. So we need to do it manually.
|
||||||
|
if util.is_oil_bufnr(filebufnr) then
|
||||||
|
M.load_oil_buffer(filebufnr)
|
||||||
|
end
|
||||||
|
|
||||||
vim.api.nvim_set_option_value("previewwindow", true, { scope = "local", win = 0 })
|
vim.api.nvim_set_option_value("previewwindow", true, { scope = "local", win = 0 })
|
||||||
|
vim.api.nvim_win_set_var(0, "oil_preview", true)
|
||||||
|
for k, v in pairs(config.preview_win.win_options) do
|
||||||
|
vim.api.nvim_set_option_value(k, v, { scope = "local", win = preview_win })
|
||||||
|
end
|
||||||
vim.w.oil_entry_id = entry.id
|
vim.w.oil_entry_id = entry.id
|
||||||
vim.w.oil_source_win = prev_win
|
vim.w.oil_source_win = prev_win
|
||||||
if is_visual_mode then
|
if has_multicursors then
|
||||||
|
hack_set_win(prev_win)
|
||||||
|
mc.restoreCursors()
|
||||||
|
elseif is_visual_mode then
|
||||||
hack_set_win(prev_win)
|
hack_set_win(prev_win)
|
||||||
-- Restore the visual selection
|
-- Restore the visual selection
|
||||||
vim.cmd.normal({ args = { "gv" }, bang = true })
|
vim.cmd.normal({ args = { "gv" }, bang = true })
|
||||||
|
|
@ -563,6 +628,7 @@ end
|
||||||
---@field split? "aboveleft"|"belowright"|"topleft"|"botright" Split modifier
|
---@field split? "aboveleft"|"belowright"|"topleft"|"botright" Split modifier
|
||||||
---@field tab? boolean Open the buffer in a new tab
|
---@field tab? boolean Open the buffer in a new tab
|
||||||
---@field close? boolean Close the original oil buffer once selection is made
|
---@field close? boolean Close the original oil buffer once selection is made
|
||||||
|
---@field handle_buffer_callback? fun(buf_id: integer) If defined, all other buffer related options here would be ignored. This callback allows you to take over the process of opening the buffer yourself.
|
||||||
|
|
||||||
---Select the entry under the cursor
|
---Select the entry under the cursor
|
||||||
---@param opts nil|oil.SelectOpts
|
---@param opts nil|oil.SelectOpts
|
||||||
|
|
@ -575,14 +641,6 @@ M.select = function(opts, callback)
|
||||||
local FIELD_META = constants.FIELD_META
|
local FIELD_META = constants.FIELD_META
|
||||||
opts = vim.tbl_extend("keep", opts or {}, {})
|
opts = vim.tbl_extend("keep", opts or {}, {})
|
||||||
|
|
||||||
if opts.preview then
|
|
||||||
vim.notify_once(
|
|
||||||
"Deprecated: do not call oil.select with preview=true. Use oil.open_preview instead.\nThis shim will be removed on 2025-01-01"
|
|
||||||
)
|
|
||||||
M.open_preview(opts, callback)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local function finish(err)
|
local function finish(err)
|
||||||
if err then
|
if err then
|
||||||
vim.notify(err, vim.log.levels.ERROR)
|
vim.notify(err, vim.log.levels.ERROR)
|
||||||
|
|
@ -680,7 +738,7 @@ M.select = function(opts, callback)
|
||||||
else
|
else
|
||||||
-- Close floating window before opening a file
|
-- Close floating window before opening a file
|
||||||
if vim.w.is_oil_win then
|
if vim.w.is_oil_win then
|
||||||
vim.api.nvim_win_close(0, false)
|
M.close()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -691,7 +749,7 @@ M.select = function(opts, callback)
|
||||||
vertical = opts.vertical,
|
vertical = opts.vertical,
|
||||||
horizontal = opts.horizontal,
|
horizontal = opts.horizontal,
|
||||||
split = opts.split,
|
split = opts.split,
|
||||||
keepalt = true,
|
keepalt = false,
|
||||||
}
|
}
|
||||||
local filebufnr = vim.fn.bufadd(normalized_url)
|
local filebufnr = vim.fn.bufadd(normalized_url)
|
||||||
local entry_is_file = not vim.endswith(normalized_url, "/")
|
local entry_is_file = not vim.endswith(normalized_url, "/")
|
||||||
|
|
@ -705,9 +763,14 @@ M.select = function(opts, callback)
|
||||||
local cmd = "buffer"
|
local cmd = "buffer"
|
||||||
if opts.tab then
|
if opts.tab then
|
||||||
vim.cmd.tabnew({ mods = mods })
|
vim.cmd.tabnew({ mods = mods })
|
||||||
|
-- Make sure the new buffer from tabnew gets cleaned up
|
||||||
|
vim.bo.bufhidden = "wipe"
|
||||||
elseif opts.split then
|
elseif opts.split then
|
||||||
cmd = "sbuffer"
|
cmd = "sbuffer"
|
||||||
end
|
end
|
||||||
|
if opts.handle_buffer_callback ~= nil then
|
||||||
|
opts.handle_buffer_callback(filebufnr)
|
||||||
|
else
|
||||||
---@diagnostic disable-next-line: param-type-mismatch
|
---@diagnostic disable-next-line: param-type-mismatch
|
||||||
local ok, err = pcall(vim.cmd, {
|
local ok, err = pcall(vim.cmd, {
|
||||||
cmd = cmd,
|
cmd = cmd,
|
||||||
|
|
@ -718,6 +781,7 @@ M.select = function(opts, callback)
|
||||||
if not ok and err and not err:match("^Vim:E325:") then
|
if not ok and err and not err:match("^Vim:E325:") then
|
||||||
vim.api.nvim_echo({ { err, "Error" } }, true, {})
|
vim.api.nvim_echo({ { err, "Error" } }, true, {})
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
open_next_entry(cb)
|
open_next_entry(cb)
|
||||||
end)
|
end)
|
||||||
|
|
@ -769,11 +833,26 @@ end
|
||||||
---@private
|
---@private
|
||||||
M._get_highlights = function()
|
M._get_highlights = function()
|
||||||
return {
|
return {
|
||||||
|
{
|
||||||
|
name = "OilEmpty",
|
||||||
|
link = "Comment",
|
||||||
|
desc = "Empty column values",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "OilHidden",
|
||||||
|
link = "Comment",
|
||||||
|
desc = "Hidden entry in an oil buffer",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name = "OilDir",
|
name = "OilDir",
|
||||||
link = "Directory",
|
link = "Directory",
|
||||||
desc = "Directory names in an oil buffer",
|
desc = "Directory names in an oil buffer",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name = "OilDirHidden",
|
||||||
|
link = "OilHidden",
|
||||||
|
desc = "Hidden directory names in an oil buffer",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name = "OilDirIcon",
|
name = "OilDirIcon",
|
||||||
link = "OilDir",
|
link = "OilDir",
|
||||||
|
|
@ -784,21 +863,61 @@ M._get_highlights = function()
|
||||||
link = "Keyword",
|
link = "Keyword",
|
||||||
desc = "Socket files in an oil buffer",
|
desc = "Socket files in an oil buffer",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name = "OilSocketHidden",
|
||||||
|
link = "OilHidden",
|
||||||
|
desc = "Hidden socket files in an oil buffer",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name = "OilLink",
|
name = "OilLink",
|
||||||
link = nil,
|
link = nil,
|
||||||
desc = "Soft links in an oil buffer",
|
desc = "Soft links in an oil buffer",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name = "OilOrphanLink",
|
||||||
|
link = nil,
|
||||||
|
desc = "Orphaned soft links in an oil buffer",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "OilLinkHidden",
|
||||||
|
link = "OilHidden",
|
||||||
|
desc = "Hidden soft links in an oil buffer",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "OilOrphanLinkHidden",
|
||||||
|
link = "OilLinkHidden",
|
||||||
|
desc = "Hidden orphaned soft links in an oil buffer",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name = "OilLinkTarget",
|
name = "OilLinkTarget",
|
||||||
link = "Comment",
|
link = "Comment",
|
||||||
desc = "The target of a soft link",
|
desc = "The target of a soft link",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name = "OilOrphanLinkTarget",
|
||||||
|
link = "DiagnosticError",
|
||||||
|
desc = "The target of an orphaned soft link",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "OilLinkTargetHidden",
|
||||||
|
link = "OilHidden",
|
||||||
|
desc = "The target of a hidden soft link",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "OilOrphanLinkTargetHidden",
|
||||||
|
link = "OilOrphanLinkTarget",
|
||||||
|
desc = "The target of an hidden orphaned soft link",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name = "OilFile",
|
name = "OilFile",
|
||||||
link = nil,
|
link = nil,
|
||||||
desc = "Normal files in an oil buffer",
|
desc = "Normal files in an oil buffer",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name = "OilFileHidden",
|
||||||
|
link = "OilHidden",
|
||||||
|
desc = "Hidden normal files in an oil buffer",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name = "OilCreate",
|
name = "OilCreate",
|
||||||
link = "DiagnosticInfo",
|
link = "DiagnosticInfo",
|
||||||
|
|
@ -914,8 +1033,9 @@ local function restore_alt_buf()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
local function load_oil_buffer(bufnr)
|
M.load_oil_buffer = function(bufnr)
|
||||||
local config = require("oil.config")
|
local config = require("oil.config")
|
||||||
local keymap_util = require("oil.keymap_util")
|
local keymap_util = require("oil.keymap_util")
|
||||||
local loading = require("oil.loading")
|
local loading = require("oil.loading")
|
||||||
|
|
@ -929,6 +1049,11 @@ local function load_oil_buffer(bufnr)
|
||||||
util.rename_buffer(bufnr, bufname)
|
util.rename_buffer(bufnr, bufname)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Early return if we're already loading or have already loaded this buffer
|
||||||
|
if loading.is_loading(bufnr) or vim.b[bufnr].filetype ~= nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
local adapter = assert(config.get_adapter_by_scheme(scheme))
|
local adapter = assert(config.get_adapter_by_scheme(scheme))
|
||||||
|
|
||||||
if vim.endswith(bufname, "/") then
|
if vim.endswith(bufname, "/") then
|
||||||
|
|
@ -1013,13 +1138,14 @@ M.setup = function(opts)
|
||||||
|
|
||||||
config.setup(opts)
|
config.setup(opts)
|
||||||
set_colors()
|
set_colors()
|
||||||
vim.api.nvim_create_user_command("Oil", function(args)
|
local callback = function(args)
|
||||||
local util = require("oil.util")
|
local util = require("oil.util")
|
||||||
if args.smods.tab == 1 then
|
if args.smods.tab > 0 then
|
||||||
vim.cmd.tabnew()
|
vim.cmd.tabnew()
|
||||||
end
|
end
|
||||||
local float = false
|
local float = false
|
||||||
local trash = false
|
local trash = false
|
||||||
|
local preview = false
|
||||||
local i = 1
|
local i = 1
|
||||||
while i <= #args.fargs do
|
while i <= #args.fargs do
|
||||||
local v = args.fargs[i]
|
local v = args.fargs[i]
|
||||||
|
|
@ -1029,6 +1155,11 @@ M.setup = function(opts)
|
||||||
elseif v == "--trash" then
|
elseif v == "--trash" then
|
||||||
trash = true
|
trash = true
|
||||||
table.remove(args.fargs, i)
|
table.remove(args.fargs, i)
|
||||||
|
elseif v == "--preview" then
|
||||||
|
-- In the future we may want to support specifying options for the preview window (e.g.
|
||||||
|
-- vertical/horizontal), but if you want that level of control maybe just use the API
|
||||||
|
preview = true
|
||||||
|
table.remove(args.fargs, i)
|
||||||
elseif v == "--progress" then
|
elseif v == "--progress" then
|
||||||
local mutator = require("oil.mutator")
|
local mutator = require("oil.mutator")
|
||||||
if mutator.is_mutating() then
|
if mutator.is_mutating() then
|
||||||
|
|
@ -1042,23 +1173,34 @@ M.setup = function(opts)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if not float and (args.smods.vertical or args.smods.split ~= "") then
|
if not float and (args.smods.vertical or args.smods.horizontal or args.smods.split ~= "") then
|
||||||
|
local range = args.count > 0 and { args.count } or nil
|
||||||
|
local cmdargs = { mods = { split = args.smods.split }, range = range }
|
||||||
if args.smods.vertical then
|
if args.smods.vertical then
|
||||||
vim.cmd.vsplit({ mods = { split = args.smods.split } })
|
vim.cmd.vsplit(cmdargs)
|
||||||
else
|
else
|
||||||
vim.cmd.split({ mods = { split = args.smods.split } })
|
vim.cmd.split(cmdargs)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local method = float and "open_float" or "open"
|
local method = float and "open_float" or "open"
|
||||||
local path = args.fargs[1]
|
local path = args.fargs[1]
|
||||||
|
local open_opts = {}
|
||||||
if trash then
|
if trash then
|
||||||
local url = M.get_url_for_path(path, false)
|
local url = M.get_url_for_path(path, false)
|
||||||
local _, new_path = util.parse_url(url)
|
local _, new_path = util.parse_url(url)
|
||||||
path = "oil-trash://" .. new_path
|
path = "oil-trash://" .. new_path
|
||||||
end
|
end
|
||||||
M[method](path)
|
if preview then
|
||||||
end, { desc = "Open oil file browser on a directory", nargs = "*", complete = "dir" })
|
open_opts.preview = {}
|
||||||
|
end
|
||||||
|
M[method](path, open_opts)
|
||||||
|
end
|
||||||
|
vim.api.nvim_create_user_command(
|
||||||
|
"Oil",
|
||||||
|
callback,
|
||||||
|
{ desc = "Open oil file browser on a directory", nargs = "*", complete = "dir", count = true }
|
||||||
|
)
|
||||||
local aug = vim.api.nvim_create_augroup("Oil", {})
|
local aug = vim.api.nvim_create_augroup("Oil", {})
|
||||||
|
|
||||||
if config.default_file_explorer then
|
if config.default_file_explorer then
|
||||||
|
|
@ -1104,7 +1246,7 @@ M.setup = function(opts)
|
||||||
pattern = scheme_pattern,
|
pattern = scheme_pattern,
|
||||||
nested = true,
|
nested = true,
|
||||||
callback = function(params)
|
callback = function(params)
|
||||||
load_oil_buffer(params.buf)
|
M.load_oil_buffer(params.buf)
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
vim.api.nvim_create_autocmd("BufWriteCmd", {
|
vim.api.nvim_create_autocmd("BufWriteCmd", {
|
||||||
|
|
@ -1139,8 +1281,7 @@ M.setup = function(opts)
|
||||||
end)
|
end)
|
||||||
vim.cmd.doautocmd({ args = { "BufWritePost", params.file }, mods = { silent = true } })
|
vim.cmd.doautocmd({ args = { "BufWritePost", params.file }, mods = { silent = true } })
|
||||||
else
|
else
|
||||||
local adapter = config.get_adapter_by_scheme(bufname)
|
local adapter = assert(config.get_adapter_by_scheme(bufname))
|
||||||
assert(adapter)
|
|
||||||
adapter.write_file(params.buf)
|
adapter.write_file(params.buf)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
@ -1167,7 +1308,10 @@ M.setup = function(opts)
|
||||||
local util = require("oil.util")
|
local util = require("oil.util")
|
||||||
local bufname = vim.api.nvim_buf_get_name(0)
|
local bufname = vim.api.nvim_buf_get_name(0)
|
||||||
local scheme = util.parse_url(bufname)
|
local scheme = util.parse_url(bufname)
|
||||||
if scheme and config.adapters[scheme] then
|
local is_oil_buf = scheme and config.adapters[scheme]
|
||||||
|
-- We want to filter out oil buffers that are not directories (i.e. ssh files)
|
||||||
|
local is_oil_dir_or_unknown = (vim.bo.filetype == "oil" or vim.bo.filetype == "")
|
||||||
|
if is_oil_buf and is_oil_dir_or_unknown then
|
||||||
local view = require("oil.view")
|
local view = require("oil.view")
|
||||||
view.maybe_set_cursor()
|
view.maybe_set_cursor()
|
||||||
-- While we are in an oil buffer, set the alternate file to the buffer we were in prior to
|
-- While we are in an oil buffer, set the alternate file to the buffer we were in prior to
|
||||||
|
|
@ -1275,7 +1419,7 @@ M.setup = function(opts)
|
||||||
local util = require("oil.util")
|
local util = require("oil.util")
|
||||||
local scheme = util.parse_url(params.file)
|
local scheme = util.parse_url(params.file)
|
||||||
if config.adapters[scheme] and vim.api.nvim_buf_line_count(params.buf) == 1 then
|
if config.adapters[scheme] and vim.api.nvim_buf_line_count(params.buf) == 1 then
|
||||||
load_oil_buffer(params.buf)
|
M.load_oil_buffer(params.buf)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
@ -1284,7 +1428,7 @@ M.setup = function(opts)
|
||||||
if maybe_hijack_directory_buffer(bufnr) and vim.v.vim_did_enter == 1 then
|
if maybe_hijack_directory_buffer(bufnr) and vim.v.vim_did_enter == 1 then
|
||||||
-- manually call load on a hijacked directory buffer if vim has already entered
|
-- manually call load on a hijacked directory buffer if vim has already entered
|
||||||
-- (the BufReadCmd will not trigger)
|
-- (the BufReadCmd will not trigger)
|
||||||
load_oil_buffer(bufnr)
|
M.load_oil_buffer(bufnr)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,18 @@ local function resolve(rhs)
|
||||||
elseif type(rhs) == "table" then
|
elseif type(rhs) == "table" then
|
||||||
local opts = vim.deepcopy(rhs)
|
local opts = vim.deepcopy(rhs)
|
||||||
-- We support passing in a `callback` key, or using the 1 index as the rhs of the keymap
|
-- We support passing in a `callback` key, or using the 1 index as the rhs of the keymap
|
||||||
local callback = resolve(opts.callback or opts[1])
|
local callback, parent_opts = resolve(opts.callback or opts[1])
|
||||||
|
|
||||||
|
-- Fall back to the parent desc, adding the opts as a string if it exists
|
||||||
|
if parent_opts.desc and not opts.desc then
|
||||||
|
if opts.opts then
|
||||||
|
opts.desc =
|
||||||
|
string.format("%s %s", parent_opts.desc, vim.inspect(opts.opts):gsub("%s+", " "))
|
||||||
|
else
|
||||||
|
opts.desc = parent_opts.desc
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local mode = opts.mode
|
local mode = opts.mode
|
||||||
if type(rhs.callback) == "string" then
|
if type(rhs.callback) == "string" then
|
||||||
local action_opts, action_mode
|
local action_opts, action_mode
|
||||||
|
|
@ -97,7 +108,7 @@ M.show_help = function(keymaps)
|
||||||
local highlights = {}
|
local highlights = {}
|
||||||
local max_line = 1
|
local max_line = 1
|
||||||
for _, entry in ipairs(keymap_entries) do
|
for _, entry in ipairs(keymap_entries) do
|
||||||
local line = string.format(" %s %s", util.rpad(entry.str, max_lhs), entry.desc)
|
local line = string.format(" %s %s", util.pad_align(entry.str, max_lhs, "left"), entry.desc)
|
||||||
max_line = math.max(max_line, vim.api.nvim_strwidth(line))
|
max_line = math.max(max_line, vim.api.nvim_strwidth(line))
|
||||||
table.insert(lines, line)
|
table.insert(lines, line)
|
||||||
local start = 1
|
local start = 1
|
||||||
|
|
|
||||||
|
|
@ -115,11 +115,13 @@ M.get_fullscreen_win_opts = function()
|
||||||
width = width - 2 -- The border consumes 1 col on each side
|
width = width - 2 -- The border consumes 1 col on each side
|
||||||
end
|
end
|
||||||
if config.float.max_width > 0 then
|
if config.float.max_width > 0 then
|
||||||
width = math.min(width, config.float.max_width)
|
local max_width = math.floor(calc_float(config.float.max_width, total_width))
|
||||||
|
width = math.min(width, max_width)
|
||||||
end
|
end
|
||||||
local height = total_height - 2 * config.float.padding
|
local height = total_height - 2 * config.float.padding
|
||||||
if config.float.max_height > 0 then
|
if config.float.max_height > 0 then
|
||||||
height = math.min(height, config.float.max_height)
|
local max_height = math.floor(calc_float(config.float.max_height, total_height))
|
||||||
|
height = math.min(height, max_height)
|
||||||
end
|
end
|
||||||
local row = math.floor((total_height - height) / 2)
|
local row = math.floor((total_height - height) / 2)
|
||||||
local col = math.floor((total_width - width) / 2) - 1 -- adjust for border width
|
local col = math.floor((total_width - width) / 2) - 1 -- adjust for border width
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,8 @@ M.set_loading = function(bufnr, is_loading)
|
||||||
M.set_loading(bufnr, false)
|
M.set_loading(bufnr, false)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local lines = { util.lpad("Loading", math.floor(width / 2) - 3), bar_iter() }
|
local lines =
|
||||||
|
{ util.pad_align("Loading", math.floor(width / 2) - 3, "right"), bar_iter() }
|
||||||
util.render_text(bufnr, lines)
|
util.render_text(bufnr, lines)
|
||||||
end)
|
end)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
126
lua/oil/log.lua
Normal file
126
lua/oil/log.lua
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
local uv = vim.uv or vim.loop
|
||||||
|
local levels_reverse = {}
|
||||||
|
for k, v in pairs(vim.log.levels) do
|
||||||
|
levels_reverse[v] = k
|
||||||
|
end
|
||||||
|
|
||||||
|
local Log = {}
|
||||||
|
|
||||||
|
---@type integer
|
||||||
|
Log.level = vim.log.levels.WARN
|
||||||
|
|
||||||
|
---@return string
|
||||||
|
Log.get_logfile = function()
|
||||||
|
local fs = require("oil.fs")
|
||||||
|
|
||||||
|
local ok, stdpath = pcall(vim.fn.stdpath, "log")
|
||||||
|
if not ok then
|
||||||
|
stdpath = vim.fn.stdpath("cache")
|
||||||
|
end
|
||||||
|
assert(type(stdpath) == "string")
|
||||||
|
return fs.join(stdpath, "oil.log")
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param level integer
|
||||||
|
---@param msg string
|
||||||
|
---@param ... any[]
|
||||||
|
---@return string
|
||||||
|
local function format(level, msg, ...)
|
||||||
|
local args = vim.F.pack_len(...)
|
||||||
|
for i = 1, args.n do
|
||||||
|
local v = args[i]
|
||||||
|
if type(v) == "table" then
|
||||||
|
args[i] = vim.inspect(v)
|
||||||
|
elseif v == nil then
|
||||||
|
args[i] = "nil"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local ok, text = pcall(string.format, msg, vim.F.unpack_len(args))
|
||||||
|
-- TODO figure out how to get formatted time inside luv callback
|
||||||
|
-- local timestr = vim.fn.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
local timestr = ""
|
||||||
|
if ok then
|
||||||
|
local str_level = levels_reverse[level]
|
||||||
|
return string.format("%s[%s] %s", timestr, str_level, text)
|
||||||
|
else
|
||||||
|
return string.format(
|
||||||
|
"%s[ERROR] error formatting log line: '%s' args %s",
|
||||||
|
timestr,
|
||||||
|
vim.inspect(msg),
|
||||||
|
vim.inspect(args)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param line string
|
||||||
|
local function write(line)
|
||||||
|
-- This will be replaced during initialization
|
||||||
|
end
|
||||||
|
|
||||||
|
local initialized = false
|
||||||
|
local function initialize()
|
||||||
|
if initialized then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
initialized = true
|
||||||
|
local filepath = Log.get_logfile()
|
||||||
|
|
||||||
|
local stat = uv.fs_stat(filepath)
|
||||||
|
if stat and stat.size > 10 * 1024 * 1024 then
|
||||||
|
local backup = filepath .. ".1"
|
||||||
|
uv.fs_unlink(backup)
|
||||||
|
uv.fs_rename(filepath, backup)
|
||||||
|
end
|
||||||
|
|
||||||
|
local parent = vim.fs.dirname(filepath)
|
||||||
|
require("oil.fs").mkdirp(parent)
|
||||||
|
|
||||||
|
local logfile, openerr = io.open(filepath, "a+")
|
||||||
|
if not logfile then
|
||||||
|
local err_msg = string.format("Failed to open oil.nvim log file: %s", openerr)
|
||||||
|
vim.notify(err_msg, vim.log.levels.ERROR)
|
||||||
|
else
|
||||||
|
write = function(line)
|
||||||
|
logfile:write(line)
|
||||||
|
logfile:write("\n")
|
||||||
|
logfile:flush()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Override the file handler e.g. for tests
|
||||||
|
---@param handler fun(line: string)
|
||||||
|
function Log.set_handler(handler)
|
||||||
|
write = handler
|
||||||
|
initialized = true
|
||||||
|
end
|
||||||
|
|
||||||
|
function Log.log(level, msg, ...)
|
||||||
|
if Log.level <= level then
|
||||||
|
initialize()
|
||||||
|
local text = format(level, msg, ...)
|
||||||
|
write(text)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Log.trace(...)
|
||||||
|
Log.log(vim.log.levels.TRACE, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Log.debug(...)
|
||||||
|
Log.log(vim.log.levels.DEBUG, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Log.info(...)
|
||||||
|
Log.log(vim.log.levels.INFO, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Log.warn(...)
|
||||||
|
Log.log(vim.log.levels.WARN, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Log.error(...)
|
||||||
|
Log.log(vim.log.levels.ERROR, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
return Log
|
||||||
|
|
@ -68,24 +68,34 @@ local function get_matching_paths(client, filters, paths)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Some language servers use forward slashes as path separators on Windows (LuaLS)
|
-- Some language servers use forward slashes as path separators on Windows (LuaLS)
|
||||||
if fs.is_windows then
|
-- We no longer need this after 0.12: https://github.com/neovim/neovim/commit/322a6d305d088420b23071c227af07b7c1beb41a
|
||||||
|
if vim.fn.has("nvim-0.12") == 0 and fs.is_windows then
|
||||||
glob = glob:gsub("/", "\\")
|
glob = glob:gsub("/", "\\")
|
||||||
end
|
end
|
||||||
|
|
||||||
---@type string|vim.lpeg.Pattern
|
---@type string|vim.lpeg.Pattern
|
||||||
local glob_to_match = glob
|
local glob_to_match = glob
|
||||||
if vim.glob and vim.glob.to_lpeg then
|
if vim.glob and vim.glob.to_lpeg then
|
||||||
|
glob = glob:gsub("{(.-)}", function(s)
|
||||||
|
local patterns = vim.split(s, ",")
|
||||||
|
local filtered = {}
|
||||||
|
for _, pat in ipairs(patterns) do
|
||||||
|
if pat ~= "" then
|
||||||
|
table.insert(filtered, pat)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if #filtered == 0 then
|
||||||
|
return ""
|
||||||
|
end
|
||||||
-- HACK around https://github.com/neovim/neovim/issues/28931
|
-- HACK around https://github.com/neovim/neovim/issues/28931
|
||||||
-- find alternations and sort them by length to try to match the longest first
|
-- find alternations and sort them by length to try to match the longest first
|
||||||
if vim.fn.has("nvim-0.11") == 0 then
|
if vim.fn.has("nvim-0.11") == 0 then
|
||||||
glob = glob:gsub("{(.*)}", function(s)
|
table.sort(filtered, function(a, b)
|
||||||
local pieces = vim.split(s, ",")
|
|
||||||
table.sort(pieces, function(a, b)
|
|
||||||
return a:len() > b:len()
|
return a:len() > b:len()
|
||||||
end)
|
end)
|
||||||
return "{" .. table.concat(pieces, ",") .. "}"
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
|
return "{" .. table.concat(filtered, ",") .. "}"
|
||||||
|
end)
|
||||||
|
|
||||||
glob_to_match = vim.glob.to_lpeg(glob)
|
glob_to_match = vim.glob.to_lpeg(glob)
|
||||||
end
|
end
|
||||||
|
|
@ -167,8 +177,13 @@ local function will_file_operation(method, capability_name, files, options)
|
||||||
}
|
}
|
||||||
end, matching_files),
|
end, matching_files),
|
||||||
}
|
}
|
||||||
---@diagnostic disable-next-line: invisible
|
local result, err
|
||||||
local result, err = client.request_sync(method, params, options.timeout_ms or 1000, 0)
|
if vim.fn.has("nvim-0.11") == 1 then
|
||||||
|
result, err = client:request_sync(method, params, options.timeout_ms or 1000, 0)
|
||||||
|
else
|
||||||
|
---@diagnostic disable-next-line: param-type-mismatch
|
||||||
|
result, err = client.request_sync(method, params, options.timeout_ms or 1000, 0)
|
||||||
|
end
|
||||||
if result and result.result then
|
if result and result.result then
|
||||||
if options.apply_edits ~= false then
|
if options.apply_edits ~= false then
|
||||||
vim.lsp.util.apply_workspace_edit(result.result, client.offset_encoding)
|
vim.lsp.util.apply_workspace_edit(result.result, client.offset_encoding)
|
||||||
|
|
@ -204,10 +219,14 @@ local function did_file_operation(method, capability_name, files)
|
||||||
}
|
}
|
||||||
end, matching_files),
|
end, matching_files),
|
||||||
}
|
}
|
||||||
---@diagnostic disable-next-line: invisible
|
if vim.fn.has("nvim-0.11") == 1 then
|
||||||
|
client:notify(method, params)
|
||||||
|
else
|
||||||
|
---@diagnostic disable-next-line: param-type-mismatch
|
||||||
client.notify(method, params)
|
client.notify(method, params)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Notify the server that the client is about to create files.
|
--- Notify the server that the client is about to create files.
|
||||||
|
|
@ -280,9 +299,15 @@ function M.will_rename_files(files, options)
|
||||||
}
|
}
|
||||||
end, matching_files),
|
end, matching_files),
|
||||||
}
|
}
|
||||||
local result, err =
|
local result, err
|
||||||
---@diagnostic disable-next-line: invisible
|
if vim.fn.has("nvim-0.11") == 1 then
|
||||||
|
result, err =
|
||||||
|
client:request_sync(ms.workspace_willRenameFiles, params, options.timeout_ms or 1000, 0)
|
||||||
|
else
|
||||||
|
result, err =
|
||||||
|
---@diagnostic disable-next-line: param-type-mismatch
|
||||||
client.request_sync(ms.workspace_willRenameFiles, params, options.timeout_ms or 1000, 0)
|
client.request_sync(ms.workspace_willRenameFiles, params, options.timeout_ms or 1000, 0)
|
||||||
|
end
|
||||||
if result and result.result then
|
if result and result.result then
|
||||||
if options.apply_edits ~= false then
|
if options.apply_edits ~= false then
|
||||||
vim.lsp.util.apply_workspace_edit(result.result, client.offset_encoding)
|
vim.lsp.util.apply_workspace_edit(result.result, client.offset_encoding)
|
||||||
|
|
@ -313,10 +338,14 @@ function M.did_rename_files(files)
|
||||||
}
|
}
|
||||||
end, matching_files),
|
end, matching_files),
|
||||||
}
|
}
|
||||||
---@diagnostic disable-next-line: invisible
|
if vim.fn.has("nvim-0.11") == 1 then
|
||||||
|
client:notify(ms.workspace_didRenameFiles, params)
|
||||||
|
else
|
||||||
|
---@diagnostic disable-next-line: param-type-mismatch
|
||||||
client.notify(ms.workspace_didRenameFiles, params)
|
client.notify(ms.workspace_didRenameFiles, params)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,8 @@ M.show = vim.schedule_wrap(function(actions, should_confirm, cb)
|
||||||
else
|
else
|
||||||
line = adapter.render_action(action)
|
line = adapter.render_action(action)
|
||||||
end
|
end
|
||||||
|
-- We can't handle lines with newlines in them
|
||||||
|
line = line:gsub("\n", "")
|
||||||
table.insert(lines, line)
|
table.insert(lines, line)
|
||||||
local line_width = vim.api.nvim_strwidth(line)
|
local line_width = vim.api.nvim_strwidth(line)
|
||||||
if line_width > max_line_width then
|
if line_width > max_line_width then
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ M.create_actions_from_diffs = function(all_diffs)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
for bufnr, diffs in pairs(all_diffs) do
|
for bufnr, diffs in pairs(all_diffs) do
|
||||||
local adapter = util.get_adapter(bufnr)
|
local adapter = util.get_adapter(bufnr, true)
|
||||||
if not adapter then
|
if not adapter then
|
||||||
error("Missing adapter")
|
error("Missing adapter")
|
||||||
end
|
end
|
||||||
|
|
@ -519,7 +519,7 @@ M.try_write_changes = function(confirm, cb)
|
||||||
if vim.bo[bufnr].modified then
|
if vim.bo[bufnr].modified then
|
||||||
local diffs, errors = parser.parse(bufnr)
|
local diffs, errors = parser.parse(bufnr)
|
||||||
all_diffs[bufnr] = diffs
|
all_diffs[bufnr] = diffs
|
||||||
local adapter = assert(util.get_adapter(bufnr))
|
local adapter = assert(util.get_adapter(bufnr, true))
|
||||||
if adapter.filter_error then
|
if adapter.filter_error then
|
||||||
errors = vim.tbl_filter(adapter.filter_error, errors)
|
errors = vim.tbl_filter(adapter.filter_error, errors)
|
||||||
end
|
end
|
||||||
|
|
@ -553,10 +553,15 @@ M.try_write_changes = function(confirm, cb)
|
||||||
{ all_errors[curbuf][1].lnum + 1, all_errors[curbuf][1].col }
|
{ all_errors[curbuf][1].lnum + 1, all_errors[curbuf][1].col }
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
---@diagnostic disable-next-line: param-type-mismatch
|
local bufnr, errs = next(all_errors)
|
||||||
local bufnr, errs = next(pairs(all_errors))
|
assert(bufnr)
|
||||||
|
assert(errs)
|
||||||
|
-- HACK: This is a workaround for the fact that we can't switch buffers in the middle of a
|
||||||
|
-- BufWriteCmd.
|
||||||
|
vim.schedule(function()
|
||||||
vim.api.nvim_win_set_buf(0, bufnr)
|
vim.api.nvim_win_set_buf(0, bufnr)
|
||||||
pcall(vim.api.nvim_win_set_cursor, 0, { errs[1].lnum + 1, errs[1].col })
|
pcall(vim.api.nvim_win_set_cursor, 0, { errs[1].lnum + 1, errs[1].col })
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
unlock()
|
unlock()
|
||||||
cb("Error parsing oil buffers")
|
cb("Error parsing oil buffers")
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ M.parse_line = function(adapter, line, column_defs)
|
||||||
local name = util.split_config(def)
|
local name = util.split_config(def)
|
||||||
local range = { start }
|
local range = { start }
|
||||||
local start_len = string.len(rem)
|
local start_len = string.len(rem)
|
||||||
value, rem = columns.parse_col(adapter, rem, def)
|
value, rem = columns.parse_col(adapter, assert(rem), def)
|
||||||
if not rem then
|
if not rem then
|
||||||
return nil, string.format("Parsing %s failed", name)
|
return nil, string.format("Parsing %s failed", name)
|
||||||
end
|
end
|
||||||
|
|
@ -156,7 +156,7 @@ M.parse = function(bufnr)
|
||||||
---@type oil.ParseError[]
|
---@type oil.ParseError[]
|
||||||
local errors = {}
|
local errors = {}
|
||||||
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
||||||
local adapter = util.get_adapter(bufnr)
|
local adapter = util.get_adapter(bufnr, true)
|
||||||
if not adapter then
|
if not adapter then
|
||||||
table.insert(errors, {
|
table.insert(errors, {
|
||||||
lnum = 0,
|
lnum = 0,
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,8 @@ M.run = function(cmd, opts, callback)
|
||||||
if err == "" then
|
if err == "" then
|
||||||
err = "Unknown error"
|
err = "Unknown error"
|
||||||
end
|
end
|
||||||
callback(err)
|
local cmd_str = type(cmd) == "string" and cmd or table.concat(cmd, " ")
|
||||||
|
callback(string.format("Error running command '%s'\n%s", cmd_str, err))
|
||||||
end
|
end
|
||||||
end),
|
end),
|
||||||
})
|
})
|
||||||
|
|
|
||||||
307
lua/oil/util.lua
307
lua/oil/util.lua
|
|
@ -21,50 +21,67 @@ end
|
||||||
---@param filename string
|
---@param filename string
|
||||||
---@return string
|
---@return string
|
||||||
M.escape_filename = function(filename)
|
M.escape_filename = function(filename)
|
||||||
local ret = filename:gsub("([%%#$])", "\\%1")
|
local ret = vim.fn.fnameescape(filename)
|
||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
local _url_escape_chars = {
|
local _url_escape_to_char = {
|
||||||
[" "] = "%20",
|
["20"] = " ",
|
||||||
["$"] = "%24",
|
["22"] = "“",
|
||||||
["&"] = "%26",
|
["23"] = "#",
|
||||||
["`"] = "%60",
|
["24"] = "$",
|
||||||
[":"] = "%3A",
|
["25"] = "%",
|
||||||
["<"] = "%3C",
|
["26"] = "&",
|
||||||
["="] = "%3D",
|
["27"] = "‘",
|
||||||
[">"] = "%3E",
|
["2B"] = "+",
|
||||||
["?"] = "%3F",
|
["2C"] = ",",
|
||||||
["["] = "%5B",
|
["2F"] = "/",
|
||||||
["\\"] = "%5C",
|
["3A"] = ":",
|
||||||
["]"] = "%5D",
|
["3B"] = ";",
|
||||||
["^"] = "%5E",
|
["3C"] = "<",
|
||||||
["{"] = "%7B",
|
["3D"] = "=",
|
||||||
["|"] = "%7C",
|
["3E"] = ">",
|
||||||
["}"] = "%7D",
|
["3F"] = "?",
|
||||||
["~"] = "%7E",
|
["40"] = "@",
|
||||||
["“"] = "%22",
|
["5B"] = "[",
|
||||||
["‘"] = "%27",
|
["5C"] = "\\",
|
||||||
["+"] = "%2B",
|
["5D"] = "]",
|
||||||
[","] = "%2C",
|
["5E"] = "^",
|
||||||
["#"] = "%23",
|
["60"] = "`",
|
||||||
["%"] = "%25",
|
["7B"] = "{",
|
||||||
["@"] = "%40",
|
["7C"] = "|",
|
||||||
["/"] = "%2F",
|
["7D"] = "}",
|
||||||
[";"] = "%3B",
|
["7E"] = "~",
|
||||||
}
|
}
|
||||||
|
local _char_to_url_escape = {}
|
||||||
|
for k, v in pairs(_url_escape_to_char) do
|
||||||
|
_char_to_url_escape[v] = "%" .. k
|
||||||
|
end
|
||||||
|
-- TODO this uri escape handling is very incomplete
|
||||||
|
|
||||||
---@param string string
|
---@param string string
|
||||||
---@return string
|
---@return string
|
||||||
M.url_escape = function(string)
|
M.url_escape = function(string)
|
||||||
return (string:gsub(".", _url_escape_chars))
|
return (string:gsub(".", _char_to_url_escape))
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param string string
|
||||||
|
---@return string
|
||||||
|
M.url_unescape = function(string)
|
||||||
|
return (
|
||||||
|
string:gsub("%%([0-9A-Fa-f][0-9A-Fa-f])", function(seq)
|
||||||
|
return _url_escape_to_char[seq:upper()] or ("%" .. seq)
|
||||||
|
end)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
|
---@param silent? boolean
|
||||||
---@return nil|oil.Adapter
|
---@return nil|oil.Adapter
|
||||||
M.get_adapter = function(bufnr)
|
M.get_adapter = function(bufnr, silent)
|
||||||
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
||||||
local adapter = config.get_adapter_by_scheme(bufname)
|
local adapter = config.get_adapter_by_scheme(bufname)
|
||||||
if not adapter then
|
if not adapter and not silent then
|
||||||
vim.notify_once(
|
vim.notify_once(
|
||||||
string.format("[oil] could not find adapter for buffer '%s://'", bufname),
|
string.format("[oil] could not find adapter for buffer '%s://'", bufname),
|
||||||
vim.log.levels.ERROR
|
vim.log.levels.ERROR
|
||||||
|
|
@ -74,34 +91,28 @@ M.get_adapter = function(bufnr)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param text string
|
---@param text string
|
||||||
---@param length nil|integer
|
---@param width integer|nil
|
||||||
---@return string
|
---@param align oil.ColumnAlign
|
||||||
M.rpad = function(text, length)
|
---@return string padded_text
|
||||||
if not length then
|
---@return integer left_padding
|
||||||
return text
|
M.pad_align = function(text, width, align)
|
||||||
|
if not width then
|
||||||
|
return text, 0
|
||||||
end
|
end
|
||||||
local textlen = vim.api.nvim_strwidth(text)
|
local text_width = vim.api.nvim_strwidth(text)
|
||||||
local delta = length - textlen
|
local total_pad = width - text_width
|
||||||
if delta > 0 then
|
if total_pad <= 0 then
|
||||||
return text .. string.rep(" ", delta)
|
return text, 0
|
||||||
else
|
|
||||||
return text
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
---@param text string
|
if align == "right" then
|
||||||
---@param length nil|integer
|
return string.rep(" ", total_pad) .. text, total_pad
|
||||||
---@return string
|
elseif align == "center" then
|
||||||
M.lpad = function(text, length)
|
local left_pad = math.floor(total_pad / 2)
|
||||||
if not length then
|
local right_pad = total_pad - left_pad
|
||||||
return text
|
return string.rep(" ", left_pad) .. text .. string.rep(" ", right_pad), left_pad
|
||||||
end
|
|
||||||
local textlen = vim.api.nvim_strwidth(text)
|
|
||||||
local delta = length - textlen
|
|
||||||
if delta > 0 then
|
|
||||||
return string.rep(" ", delta) .. text
|
|
||||||
else
|
else
|
||||||
return text
|
return text .. string.rep(" ", total_pad), 0
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -157,8 +168,10 @@ M.rename_buffer = function(src_bufnr, dest_buf_name)
|
||||||
-- This will fail if the dest buf name already exists
|
-- This will fail if the dest buf name already exists
|
||||||
local ok = pcall(vim.api.nvim_buf_set_name, src_bufnr, dest_buf_name)
|
local ok = pcall(vim.api.nvim_buf_set_name, src_bufnr, dest_buf_name)
|
||||||
if ok then
|
if ok then
|
||||||
-- Renaming the buffer creates a new buffer with the old name. Find it and delete it.
|
-- Renaming the buffer creates a new buffer with the old name.
|
||||||
vim.api.nvim_buf_delete(vim.fn.bufadd(bufname), {})
|
-- Find it and try to delete it, but don't if the buffer is in a context
|
||||||
|
-- where Neovim doesn't allow buffer modifications.
|
||||||
|
pcall(vim.api.nvim_buf_delete, vim.fn.bufadd(bufname), {})
|
||||||
if altbuf and vim.api.nvim_buf_is_valid(altbuf) then
|
if altbuf and vim.api.nvim_buf_is_valid(altbuf) then
|
||||||
vim.fn.setreg("#", altbuf)
|
vim.fn.setreg("#", altbuf)
|
||||||
end
|
end
|
||||||
|
|
@ -195,6 +208,18 @@ M.rename_buffer = function(src_bufnr, dest_buf_name)
|
||||||
-- Try to delete, but don't if the buffer has changes
|
-- Try to delete, but don't if the buffer has changes
|
||||||
pcall(vim.api.nvim_buf_delete, src_bufnr, {})
|
pcall(vim.api.nvim_buf_delete, src_bufnr, {})
|
||||||
end
|
end
|
||||||
|
-- Renaming a buffer won't load the undo file, so we need to do that manually
|
||||||
|
if vim.bo[dest_bufnr].undofile then
|
||||||
|
vim.api.nvim_buf_call(dest_bufnr, function()
|
||||||
|
vim.cmd.rundo({
|
||||||
|
args = { vim.fn.undofile(dest_buf_name) },
|
||||||
|
magic = { file = false, bar = false },
|
||||||
|
mods = {
|
||||||
|
emsg_silent = true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
@ -283,11 +308,15 @@ M.split_config = function(name_or_config)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@alias oil.ColumnAlign "left"|"center"|"right"
|
||||||
|
|
||||||
---@param lines oil.TextChunk[][]
|
---@param lines oil.TextChunk[][]
|
||||||
---@param col_width integer[]
|
---@param col_width integer[]
|
||||||
|
---@param col_align? oil.ColumnAlign[]
|
||||||
---@return string[]
|
---@return string[]
|
||||||
---@return any[][] List of highlights {group, lnum, col_start, col_end}
|
---@return any[][] List of highlights {group, lnum, col_start, col_end}
|
||||||
M.render_table = function(lines, col_width)
|
M.render_table = function(lines, col_width, col_align)
|
||||||
|
col_align = col_align or {}
|
||||||
local str_lines = {}
|
local str_lines = {}
|
||||||
local highlights = {}
|
local highlights = {}
|
||||||
for _, cols in ipairs(lines) do
|
for _, cols in ipairs(lines) do
|
||||||
|
|
@ -301,9 +330,12 @@ M.render_table = function(lines, col_width)
|
||||||
else
|
else
|
||||||
text = chunk
|
text = chunk
|
||||||
end
|
end
|
||||||
text = M.rpad(text, col_width[i])
|
|
||||||
|
local unpadded_len = text:len()
|
||||||
|
local padding
|
||||||
|
text, padding = M.pad_align(text, col_width[i], col_align[i] or "left")
|
||||||
|
|
||||||
table.insert(pieces, text)
|
table.insert(pieces, text)
|
||||||
local col_end = col + text:len() + 1
|
|
||||||
if hl then
|
if hl then
|
||||||
if type(hl) == "table" then
|
if type(hl) == "table" then
|
||||||
-- hl has the form { [1]: hl_name, [2]: col_start, [3]: col_end }[]
|
-- hl has the form { [1]: hl_name, [2]: col_start, [3]: col_end }[]
|
||||||
|
|
@ -313,15 +345,15 @@ M.render_table = function(lines, col_width)
|
||||||
table.insert(highlights, {
|
table.insert(highlights, {
|
||||||
sub_hl[1],
|
sub_hl[1],
|
||||||
#str_lines,
|
#str_lines,
|
||||||
col + sub_hl[2],
|
col + padding + sub_hl[2],
|
||||||
col + sub_hl[3],
|
col + padding + sub_hl[3],
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
table.insert(highlights, { hl, #str_lines, col, col_end })
|
table.insert(highlights, { hl, #str_lines, col + padding, col + padding + unpadded_len })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
col = col_end
|
col = col + text:len() + 1
|
||||||
end
|
end
|
||||||
table.insert(str_lines, table.concat(pieces, " "))
|
table.insert(str_lines, table.concat(pieces, " "))
|
||||||
end
|
end
|
||||||
|
|
@ -334,7 +366,12 @@ M.set_highlights = function(bufnr, highlights)
|
||||||
local ns = vim.api.nvim_create_namespace("Oil")
|
local ns = vim.api.nvim_create_namespace("Oil")
|
||||||
vim.api.nvim_buf_clear_namespace(bufnr, ns, 0, -1)
|
vim.api.nvim_buf_clear_namespace(bufnr, ns, 0, -1)
|
||||||
for _, hl in ipairs(highlights) do
|
for _, hl in ipairs(highlights) do
|
||||||
vim.api.nvim_buf_add_highlight(bufnr, ns, unpack(hl))
|
local group, line, col_start, col_end = unpack(hl)
|
||||||
|
vim.api.nvim_buf_set_extmark(bufnr, ns, line, col_start, {
|
||||||
|
end_col = col_end,
|
||||||
|
hl_group = group,
|
||||||
|
strict = false,
|
||||||
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -347,7 +384,8 @@ M.addslash = function(path, os_slash)
|
||||||
slash = "\\"
|
slash = "\\"
|
||||||
end
|
end
|
||||||
|
|
||||||
if not vim.endswith(path, slash) then
|
local endslash = path:match(slash .. "$")
|
||||||
|
if not endslash then
|
||||||
return path .. slash
|
return path .. slash
|
||||||
else
|
else
|
||||||
return path
|
return path
|
||||||
|
|
@ -487,10 +525,7 @@ end
|
||||||
---@return oil.Adapter
|
---@return oil.Adapter
|
||||||
---@return nil|oil.CrossAdapterAction
|
---@return nil|oil.CrossAdapterAction
|
||||||
M.get_adapter_for_action = function(action)
|
M.get_adapter_for_action = function(action)
|
||||||
local adapter = config.get_adapter_by_scheme(action.url or action.src_url)
|
local adapter = assert(config.get_adapter_by_scheme(action.url or action.src_url))
|
||||||
if not adapter then
|
|
||||||
error("no adapter found")
|
|
||||||
end
|
|
||||||
if action.dest_url then
|
if action.dest_url then
|
||||||
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
||||||
if adapter ~= dest_adapter then
|
if adapter ~= dest_adapter then
|
||||||
|
|
@ -611,11 +646,7 @@ M.render_text = function(bufnr, text, opts)
|
||||||
pcall(vim.api.nvim_buf_set_lines, bufnr, 0, -1, false, lines)
|
pcall(vim.api.nvim_buf_set_lines, bufnr, 0, -1, false, lines)
|
||||||
vim.bo[bufnr].modifiable = false
|
vim.bo[bufnr].modifiable = false
|
||||||
vim.bo[bufnr].modified = false
|
vim.bo[bufnr].modified = false
|
||||||
local ns = vim.api.nvim_create_namespace("Oil")
|
M.set_highlights(bufnr, highlights)
|
||||||
vim.api.nvim_buf_clear_namespace(bufnr, ns, 0, -1)
|
|
||||||
for _, hl in ipairs(highlights) do
|
|
||||||
vim.api.nvim_buf_add_highlight(bufnr, ns, unpack(hl))
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---Run a function in the context of a full-editor window
|
---Run a function in the context of a full-editor window
|
||||||
|
|
@ -643,8 +674,12 @@ end
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
---@return boolean
|
---@return boolean
|
||||||
M.is_oil_bufnr = function(bufnr)
|
M.is_oil_bufnr = function(bufnr)
|
||||||
if vim.bo[bufnr].filetype == "oil" then
|
local filetype = vim.bo[bufnr].filetype
|
||||||
|
if filetype == "oil" then
|
||||||
return true
|
return true
|
||||||
|
elseif filetype ~= "" then
|
||||||
|
-- If the filetype is set and is NOT "oil", then it's not an oil buffer
|
||||||
|
return false
|
||||||
end
|
end
|
||||||
local scheme = M.parse_url(vim.api.nvim_buf_get_name(bufnr))
|
local scheme = M.parse_url(vim.api.nvim_buf_get_name(bufnr))
|
||||||
return config.adapters[scheme] or config.adapter_aliases[scheme]
|
return config.adapters[scheme] or config.adapter_aliases[scheme]
|
||||||
|
|
@ -667,10 +702,17 @@ M.hack_around_termopen_autocmd = function(prev_mode)
|
||||||
end, 10)
|
end, 10)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param opts? {include_not_owned?: boolean}
|
||||||
---@return nil|integer
|
---@return nil|integer
|
||||||
M.get_preview_win = function()
|
M.get_preview_win = function(opts)
|
||||||
|
opts = opts or {}
|
||||||
|
|
||||||
for _, winid in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
|
for _, winid in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
|
||||||
if vim.api.nvim_win_is_valid(winid) and vim.wo[winid].previewwindow then
|
if
|
||||||
|
vim.api.nvim_win_is_valid(winid)
|
||||||
|
and vim.wo[winid].previewwindow
|
||||||
|
and (opts.include_not_owned or vim.w[winid]["oil_preview"])
|
||||||
|
then
|
||||||
return winid
|
return winid
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -745,7 +787,7 @@ end
|
||||||
|
|
||||||
---Send files from the current oil directory to quickfix
|
---Send files from the current oil directory to quickfix
|
||||||
---based on the provided options.
|
---based on the provided options.
|
||||||
---@param opts {target?: "qflist"|"loclist", mode?: "r"|"a"}
|
---@param opts {target?: "qflist"|"loclist", action?: "r"|"a", only_matching_search?: boolean}
|
||||||
M.send_to_quickfix = function(opts)
|
M.send_to_quickfix = function(opts)
|
||||||
if type(opts) ~= "table" then
|
if type(opts) ~= "table" then
|
||||||
opts = {}
|
opts = {}
|
||||||
|
|
@ -759,10 +801,11 @@ M.send_to_quickfix = function(opts)
|
||||||
if not range then
|
if not range then
|
||||||
range = { start_lnum = 1, end_lnum = vim.fn.line("$") }
|
range = { start_lnum = 1, end_lnum = vim.fn.line("$") }
|
||||||
end
|
end
|
||||||
|
local match_all = not opts.only_matching_search
|
||||||
local qf_entries = {}
|
local qf_entries = {}
|
||||||
for i = range.start_lnum, range.end_lnum do
|
for i = range.start_lnum, range.end_lnum do
|
||||||
local entry = oil.get_entry_on_line(0, i)
|
local entry = oil.get_entry_on_line(0, i)
|
||||||
if entry and entry.type == "file" then
|
if entry and entry.type == "file" and (match_all or M.is_matching(entry)) then
|
||||||
local qf_entry = {
|
local qf_entry = {
|
||||||
filename = dir .. entry.name,
|
filename = dir .. entry.name,
|
||||||
lnum = 1,
|
lnum = 1,
|
||||||
|
|
@ -778,11 +821,13 @@ M.send_to_quickfix = function(opts)
|
||||||
end
|
end
|
||||||
vim.api.nvim_exec_autocmds("QuickFixCmdPre", {})
|
vim.api.nvim_exec_autocmds("QuickFixCmdPre", {})
|
||||||
local qf_title = "oil files"
|
local qf_title = "oil files"
|
||||||
local mode = opts.mode == "a" and "a" or "r"
|
local action = opts.action == "a" and "a" or "r"
|
||||||
if opts.target == "loclist" then
|
if opts.target == "loclist" then
|
||||||
vim.fn.setloclist(0, {}, mode, { title = qf_title, items = qf_entries })
|
vim.fn.setloclist(0, {}, action, { title = qf_title, items = qf_entries })
|
||||||
|
vim.cmd.lopen()
|
||||||
else
|
else
|
||||||
vim.fn.setqflist({}, mode, { title = qf_title, items = qf_entries })
|
vim.fn.setqflist({}, action, { title = qf_title, items = qf_entries })
|
||||||
|
vim.cmd.copen()
|
||||||
end
|
end
|
||||||
vim.api.nvim_exec_autocmds("QuickFixCmdPost", {})
|
vim.api.nvim_exec_autocmds("QuickFixCmdPost", {})
|
||||||
end
|
end
|
||||||
|
|
@ -809,6 +854,19 @@ M.get_visual_range = function()
|
||||||
return { start_lnum = start_lnum, end_lnum = end_lnum }
|
return { start_lnum = start_lnum, end_lnum = end_lnum }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param entry oil.Entry
|
||||||
|
---@return boolean
|
||||||
|
M.is_matching = function(entry)
|
||||||
|
-- if search highlightig is not enabled, all files are considered to match
|
||||||
|
local search_highlighting_is_off = (vim.v.hlsearch == 0)
|
||||||
|
if search_highlighting_is_off then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
local pattern = vim.fn.getreg("/")
|
||||||
|
local position_of_match = vim.fn.match(entry.name, pattern)
|
||||||
|
return position_of_match ~= -1
|
||||||
|
end
|
||||||
|
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
---@param callback fun()
|
---@param callback fun()
|
||||||
M.run_after_load = function(bufnr, callback)
|
M.run_after_load = function(bufnr, callback)
|
||||||
|
|
@ -852,7 +910,7 @@ M.get_edit_path = function(bufnr, entry, callback)
|
||||||
|
|
||||||
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
||||||
local scheme, dir = M.parse_url(bufname)
|
local scheme, dir = M.parse_url(bufname)
|
||||||
local adapter = M.get_adapter(bufnr)
|
local adapter = M.get_adapter(bufnr, true)
|
||||||
assert(scheme and dir and adapter)
|
assert(scheme and dir and adapter)
|
||||||
|
|
||||||
local url = scheme .. dir .. entry.name
|
local url = scheme .. dir .. entry.name
|
||||||
|
|
@ -896,4 +954,85 @@ M.get_icon_provider = function()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---Read a buffer into a scratch buffer and apply syntactic highlighting when possible
|
||||||
|
---@param path string The path to the file to read
|
||||||
|
---@param preview_method oil.PreviewMethod
|
||||||
|
---@return nil|integer
|
||||||
|
M.read_file_to_scratch_buffer = function(path, preview_method)
|
||||||
|
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||||
|
if bufnr == 0 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
vim.bo[bufnr].bufhidden = "wipe"
|
||||||
|
vim.bo[bufnr].buftype = "nofile"
|
||||||
|
|
||||||
|
local has_lines, read_res
|
||||||
|
if preview_method == "fast_scratch" then
|
||||||
|
has_lines, read_res = pcall(vim.fn.readfile, path, "", vim.o.lines)
|
||||||
|
else
|
||||||
|
has_lines, read_res = pcall(vim.fn.readfile, path)
|
||||||
|
end
|
||||||
|
local lines = has_lines and vim.split(table.concat(read_res, "\n"), "\n") or {}
|
||||||
|
|
||||||
|
local ok = pcall(vim.api.nvim_buf_set_lines, bufnr, 0, -1, false, lines)
|
||||||
|
if not ok then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local ft = vim.filetype.match({ filename = path, buf = bufnr })
|
||||||
|
if ft and ft ~= "" and vim.treesitter.language.get_lang then
|
||||||
|
local lang = vim.treesitter.language.get_lang(ft)
|
||||||
|
if not pcall(vim.treesitter.start, bufnr, lang) then
|
||||||
|
vim.bo[bufnr].syntax = ft
|
||||||
|
else
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Replace the scratch buffer with a real buffer if we enter it
|
||||||
|
vim.api.nvim_create_autocmd("BufEnter", {
|
||||||
|
desc = "oil.nvim replace scratch buffer with real buffer",
|
||||||
|
buffer = bufnr,
|
||||||
|
callback = function()
|
||||||
|
local winid = vim.api.nvim_get_current_win()
|
||||||
|
-- Have to schedule this so all the FileType, etc autocmds will fire
|
||||||
|
vim.schedule(function()
|
||||||
|
if vim.api.nvim_get_current_win() == winid then
|
||||||
|
vim.cmd.edit({ args = { path } })
|
||||||
|
|
||||||
|
-- If we're still in a preview window, make sure this buffer still gets treated as a
|
||||||
|
-- preview
|
||||||
|
if vim.wo.previewwindow then
|
||||||
|
vim.bo.bufhidden = "wipe"
|
||||||
|
vim.b.oil_preview_buffer = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
return bufnr
|
||||||
|
end
|
||||||
|
|
||||||
|
local _regcache = {}
|
||||||
|
---Check if a file matches a BufReadCmd autocmd
|
||||||
|
---@param filename string
|
||||||
|
---@return boolean
|
||||||
|
M.file_matches_bufreadcmd = function(filename)
|
||||||
|
local autocmds = vim.api.nvim_get_autocmds({
|
||||||
|
event = "BufReadCmd",
|
||||||
|
})
|
||||||
|
for _, au in ipairs(autocmds) do
|
||||||
|
local pat = _regcache[au.pattern]
|
||||||
|
if not pat then
|
||||||
|
pat = vim.fn.glob2regpat(au.pattern)
|
||||||
|
_regcache[au.pattern] = pat
|
||||||
|
end
|
||||||
|
|
||||||
|
if vim.fn.match(filename, pat) >= 0 then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|
|
||||||
278
lua/oil/view.lua
278
lua/oil/view.lua
|
|
@ -19,10 +19,16 @@ local last_cursor_entry = {}
|
||||||
|
|
||||||
---@param name string
|
---@param name string
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
---@return boolean
|
---@return boolean display
|
||||||
|
---@return boolean is_hidden Whether the file is classified as a hidden file
|
||||||
M.should_display = function(name, bufnr)
|
M.should_display = function(name, bufnr)
|
||||||
return not config.view_options.is_always_hidden(name, bufnr)
|
if config.view_options.is_always_hidden(name, bufnr) then
|
||||||
and (config.view_options.show_hidden or not config.view_options.is_hidden_file(name, bufnr))
|
return false, true
|
||||||
|
else
|
||||||
|
local is_hidden = config.view_options.is_hidden_file(name, bufnr)
|
||||||
|
local display = config.view_options.show_hidden or not is_hidden
|
||||||
|
return display, is_hidden
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param bufname string
|
---@param bufname string
|
||||||
|
|
@ -140,7 +146,7 @@ M.unlock_buffers = function()
|
||||||
buffers_locked = false
|
buffers_locked = false
|
||||||
for bufnr in pairs(session) do
|
for bufnr in pairs(session) do
|
||||||
if vim.api.nvim_buf_is_loaded(bufnr) then
|
if vim.api.nvim_buf_is_loaded(bufnr) then
|
||||||
local adapter = util.get_adapter(bufnr)
|
local adapter = util.get_adapter(bufnr, true)
|
||||||
if adapter then
|
if adapter then
|
||||||
vim.bo[bufnr].modifiable = adapter.is_modifiable(bufnr)
|
vim.bo[bufnr].modifiable = adapter.is_modifiable(bufnr)
|
||||||
end
|
end
|
||||||
|
|
@ -179,9 +185,18 @@ end
|
||||||
|
|
||||||
M.set_win_options = function()
|
M.set_win_options = function()
|
||||||
local winid = vim.api.nvim_get_current_win()
|
local winid = vim.api.nvim_get_current_win()
|
||||||
|
|
||||||
|
-- work around https://github.com/neovim/neovim/pull/27422
|
||||||
|
vim.api.nvim_set_option_value("foldmethod", "manual", { scope = "local", win = winid })
|
||||||
|
|
||||||
for k, v in pairs(config.win_options) do
|
for k, v in pairs(config.win_options) do
|
||||||
vim.api.nvim_set_option_value(k, v, { scope = "local", win = winid })
|
vim.api.nvim_set_option_value(k, v, { scope = "local", win = winid })
|
||||||
end
|
end
|
||||||
|
if vim.wo[winid].previewwindow then -- apply preview window options last
|
||||||
|
for k, v in pairs(config.preview_win.win_options) do
|
||||||
|
vim.api.nvim_set_option_value(k, v, { scope = "local", win = winid })
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---Get a list of visible oil buffers and a list of hidden oil buffers
|
---Get a list of visible oil buffers and a list of hidden oil buffers
|
||||||
|
|
@ -242,35 +257,65 @@ local function get_first_mutable_column_col(adapter, ranges)
|
||||||
return min_col
|
return min_col
|
||||||
end
|
end
|
||||||
|
|
||||||
---Force cursor to be after hidden/immutable columns
|
--- @param bufnr integer
|
||||||
local function constrain_cursor()
|
--- @param adapter oil.Adapter
|
||||||
if not config.constrain_cursor then
|
--- @param mode false|"name"|"editable"
|
||||||
return
|
--- @param cur integer[]
|
||||||
end
|
--- @return integer[] | nil
|
||||||
|
local function calc_constrained_cursor_pos(bufnr, adapter, mode, cur)
|
||||||
local parser = require("oil.mutator.parser")
|
local parser = require("oil.mutator.parser")
|
||||||
|
local line = vim.api.nvim_buf_get_lines(bufnr, cur[1] - 1, cur[1], true)[1]
|
||||||
local adapter = util.get_adapter(0)
|
|
||||||
if not adapter then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local cur = vim.api.nvim_win_get_cursor(0)
|
|
||||||
local line = vim.api.nvim_buf_get_lines(0, cur[1] - 1, cur[1], true)[1]
|
|
||||||
local column_defs = columns.get_supported_columns(adapter)
|
local column_defs = columns.get_supported_columns(adapter)
|
||||||
local result = parser.parse_line(adapter, line, column_defs)
|
local result = parser.parse_line(adapter, line, column_defs)
|
||||||
if result and result.ranges then
|
if result and result.ranges then
|
||||||
local min_col
|
local min_col
|
||||||
if config.constrain_cursor == "editable" then
|
if mode == "editable" then
|
||||||
min_col = get_first_mutable_column_col(adapter, result.ranges)
|
min_col = get_first_mutable_column_col(adapter, result.ranges)
|
||||||
elseif config.constrain_cursor == "name" then
|
elseif mode == "name" then
|
||||||
min_col = result.ranges.name[1]
|
min_col = result.ranges.name[1]
|
||||||
else
|
else
|
||||||
error(
|
error(string.format('Unexpected value "%s" for option constrain_cursor', mode))
|
||||||
string.format('Unexpected value "%s" for option constrain_cursor', config.constrain_cursor)
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
if cur[2] < min_col then
|
if cur[2] < min_col then
|
||||||
vim.api.nvim_win_set_cursor(0, { cur[1], min_col })
|
return { cur[1], min_col }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Force cursor to be after hidden/immutable columns
|
||||||
|
---@param bufnr integer
|
||||||
|
---@param mode false|"name"|"editable"
|
||||||
|
local function constrain_cursor(bufnr, mode)
|
||||||
|
if not mode then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if bufnr ~= vim.api.nvim_get_current_buf() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local adapter = util.get_adapter(bufnr, true)
|
||||||
|
if not adapter then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local mc = package.loaded["multicursor-nvim"]
|
||||||
|
if mc then
|
||||||
|
mc.onSafeState(function()
|
||||||
|
mc.action(function(ctx)
|
||||||
|
ctx:forEachCursor(function(cursor)
|
||||||
|
local new_cur =
|
||||||
|
calc_constrained_cursor_pos(bufnr, adapter, mode, { cursor:line(), cursor:col() - 1 })
|
||||||
|
if new_cur then
|
||||||
|
cursor:setPos({ new_cur[1], new_cur[2] + 1 })
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end, { once = true })
|
||||||
|
else
|
||||||
|
local cur = vim.api.nvim_win_get_cursor(0)
|
||||||
|
local new_cur = calc_constrained_cursor_pos(bufnr, adapter, mode, cur)
|
||||||
|
if new_cur then
|
||||||
|
vim.api.nvim_win_set_cursor(0, new_cur)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -282,7 +327,7 @@ local function redraw_trash_virtual_text(bufnr)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local parser = require("oil.mutator.parser")
|
local parser = require("oil.mutator.parser")
|
||||||
local adapter = util.get_adapter(bufnr)
|
local adapter = util.get_adapter(bufnr, true)
|
||||||
if not adapter or adapter.name ~= "trash" then
|
if not adapter or adapter.name ~= "trash" then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
@ -392,7 +437,7 @@ M.initialize = function(bufnr)
|
||||||
callback = function()
|
callback = function()
|
||||||
-- For some reason the cursor bounces back to its original position,
|
-- For some reason the cursor bounces back to its original position,
|
||||||
-- so we have to defer the call
|
-- so we have to defer the call
|
||||||
vim.schedule(constrain_cursor)
|
vim.schedule_wrap(constrain_cursor)(bufnr, config.constrain_cursor)
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
vim.api.nvim_create_autocmd({ "CursorMoved", "ModeChanged" }, {
|
vim.api.nvim_create_autocmd({ "CursorMoved", "ModeChanged" }, {
|
||||||
|
|
@ -405,7 +450,7 @@ M.initialize = function(bufnr)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
constrain_cursor()
|
constrain_cursor(bufnr, config.constrain_cursor)
|
||||||
|
|
||||||
if config.preview_win.update_on_cursor_moved then
|
if config.preview_win.update_on_cursor_moved then
|
||||||
-- Debounce and update the preview window
|
-- Debounce and update the preview window
|
||||||
|
|
@ -442,7 +487,7 @@ M.initialize = function(bufnr)
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
local adapter = util.get_adapter(bufnr)
|
local adapter = util.get_adapter(bufnr, true)
|
||||||
|
|
||||||
-- Set up a watcher that will refresh the directory
|
-- Set up a watcher that will refresh the directory
|
||||||
if
|
if
|
||||||
|
|
@ -532,8 +577,9 @@ M.initialize = function(bufnr)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param adapter oil.Adapter
|
---@param adapter oil.Adapter
|
||||||
|
---@param num_entries integer
|
||||||
---@return fun(a: oil.InternalEntry, b: oil.InternalEntry): boolean
|
---@return fun(a: oil.InternalEntry, b: oil.InternalEntry): boolean
|
||||||
local function get_sort_function(adapter)
|
local function get_sort_function(adapter, num_entries)
|
||||||
local idx_funs = {}
|
local idx_funs = {}
|
||||||
local sort_config = config.view_options.sort
|
local sort_config = config.view_options.sort
|
||||||
|
|
||||||
|
|
@ -555,7 +601,9 @@ local function get_sort_function(adapter)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
local col = columns.get_column(adapter, col_name)
|
local col = columns.get_column(adapter, col_name)
|
||||||
if col and col.get_sort_value then
|
if col and col.create_sort_value_factory then
|
||||||
|
table.insert(idx_funs, { col.create_sort_value_factory(num_entries), order })
|
||||||
|
elseif col and col.get_sort_value then
|
||||||
table.insert(idx_funs, { col.get_sort_value, order })
|
table.insert(idx_funs, { col.get_sort_value, order })
|
||||||
else
|
else
|
||||||
vim.notify_once(
|
vim.notify_once(
|
||||||
|
|
@ -566,7 +614,7 @@ local function get_sort_function(adapter)
|
||||||
end
|
end
|
||||||
return function(a, b)
|
return function(a, b)
|
||||||
for _, sort_fn in ipairs(idx_funs) do
|
for _, sort_fn in ipairs(idx_funs) do
|
||||||
local get_sort_value, order = unpack(sort_fn)
|
local get_sort_value, order = sort_fn[1], sort_fn[2]
|
||||||
local a_val = get_sort_value(a)
|
local a_val = get_sort_value(a)
|
||||||
local b_val = get_sort_value(b)
|
local b_val = get_sort_value(b)
|
||||||
if a_val ~= b_val then
|
if a_val ~= b_val then
|
||||||
|
|
@ -599,14 +647,17 @@ local function render_buffer(bufnr, opts)
|
||||||
jump_first = false,
|
jump_first = false,
|
||||||
})
|
})
|
||||||
local scheme = util.parse_url(bufname)
|
local scheme = util.parse_url(bufname)
|
||||||
local adapter = util.get_adapter(bufnr)
|
local adapter = util.get_adapter(bufnr, true)
|
||||||
if not scheme or not adapter then
|
if not scheme or not adapter then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
local entries = cache.list_url(bufname)
|
local entries = cache.list_url(bufname)
|
||||||
local entry_list = vim.tbl_values(entries)
|
local entry_list = vim.tbl_values(entries)
|
||||||
|
|
||||||
table.sort(entry_list, get_sort_function(adapter))
|
-- Only sort the entries once we have them all
|
||||||
|
if not vim.b[bufnr].oil_rendering then
|
||||||
|
table.sort(entry_list, get_sort_function(adapter, #entry_list))
|
||||||
|
end
|
||||||
|
|
||||||
local jump_idx
|
local jump_idx
|
||||||
if opts.jump_first then
|
if opts.jump_first then
|
||||||
|
|
@ -617,30 +668,34 @@ local function render_buffer(bufnr, opts)
|
||||||
local column_defs = columns.get_supported_columns(scheme)
|
local column_defs = columns.get_supported_columns(scheme)
|
||||||
local line_table = {}
|
local line_table = {}
|
||||||
local col_width = {}
|
local col_width = {}
|
||||||
for i in ipairs(column_defs) do
|
local col_align = {}
|
||||||
|
for i, col_def in ipairs(column_defs) do
|
||||||
col_width[i + 1] = 1
|
col_width[i + 1] = 1
|
||||||
|
local _, conf = util.split_config(col_def)
|
||||||
|
col_align[i + 1] = conf and conf.align or "left"
|
||||||
end
|
end
|
||||||
|
|
||||||
if M.should_display("..", bufnr) then
|
if M.should_display("..", bufnr) then
|
||||||
local cols = M.format_entry_cols({ 0, "..", "directory" }, column_defs, col_width, adapter)
|
local cols =
|
||||||
|
M.format_entry_cols({ 0, "..", "directory" }, column_defs, col_width, adapter, true, bufnr)
|
||||||
table.insert(line_table, cols)
|
table.insert(line_table, cols)
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, entry in ipairs(entry_list) do
|
for _, entry in ipairs(entry_list) do
|
||||||
if M.should_display(entry[FIELD_NAME], bufnr) then
|
local should_display, is_hidden = M.should_display(entry[FIELD_NAME], bufnr)
|
||||||
local cols = M.format_entry_cols(entry, column_defs, col_width, adapter)
|
if should_display then
|
||||||
|
local cols = M.format_entry_cols(entry, column_defs, col_width, adapter, is_hidden, bufnr)
|
||||||
table.insert(line_table, cols)
|
table.insert(line_table, cols)
|
||||||
|
|
||||||
local name = entry[FIELD_NAME]
|
local name = entry[FIELD_NAME]
|
||||||
if seek_after_render == name then
|
if seek_after_render == name then
|
||||||
seek_after_render_found = true
|
seek_after_render_found = true
|
||||||
jump_idx = #line_table
|
jump_idx = #line_table
|
||||||
M.set_last_cursor(bufname, nil)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local lines, highlights = util.render_table(line_table, col_width)
|
local lines, highlights = util.render_table(line_table, col_width, col_align)
|
||||||
|
|
||||||
vim.bo[bufnr].modifiable = true
|
vim.bo[bufnr].modifiable = true
|
||||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, lines)
|
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, lines)
|
||||||
|
|
@ -653,8 +708,8 @@ local function render_buffer(bufnr, opts)
|
||||||
vim.schedule(function()
|
vim.schedule(function()
|
||||||
for _, winid in ipairs(vim.api.nvim_list_wins()) do
|
for _, winid in ipairs(vim.api.nvim_list_wins()) do
|
||||||
if vim.api.nvim_win_is_valid(winid) and vim.api.nvim_win_get_buf(winid) == bufnr then
|
if vim.api.nvim_win_is_valid(winid) and vim.api.nvim_win_get_buf(winid) == bufnr then
|
||||||
-- If we're not jumping to a specific lnum, use the current lnum so we can adjust the col
|
if jump_idx then
|
||||||
local lnum = jump_idx or vim.api.nvim_win_get_cursor(winid)[1]
|
local lnum = jump_idx
|
||||||
local line = vim.api.nvim_buf_get_lines(bufnr, lnum - 1, lnum, true)[1]
|
local line = vim.api.nvim_buf_get_lines(bufnr, lnum - 1, lnum, true)[1]
|
||||||
local id_str = line:match("^/(%d+)")
|
local id_str = line:match("^/(%d+)")
|
||||||
local id = tonumber(id_str)
|
local id = tonumber(id_str)
|
||||||
|
|
@ -664,47 +719,24 @@ local function render_buffer(bufnr, opts)
|
||||||
local name = entry[FIELD_NAME]
|
local name = entry[FIELD_NAME]
|
||||||
local col = line:find(name, 1, true) or (id_str:len() + 1)
|
local col = line:find(name, 1, true) or (id_str:len() + 1)
|
||||||
vim.api.nvim_win_set_cursor(winid, { lnum, col - 1 })
|
vim.api.nvim_win_set_cursor(winid, { lnum, col - 1 })
|
||||||
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
constrain_cursor(bufnr, "name")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
return seek_after_render_found
|
return seek_after_render_found
|
||||||
end
|
end
|
||||||
|
|
||||||
---@private
|
---@param name string
|
||||||
---@param entry oil.InternalEntry
|
---@param meta? table
|
||||||
---@param column_defs table[]
|
---@return string filename
|
||||||
---@param col_width integer[]
|
---@return string|nil link_target
|
||||||
---@param adapter oil.Adapter
|
local function get_link_text(name, meta)
|
||||||
---@return oil.TextChunk[]
|
|
||||||
M.format_entry_cols = function(entry, column_defs, col_width, adapter)
|
|
||||||
local name = entry[FIELD_NAME]
|
|
||||||
local meta = entry[FIELD_META]
|
|
||||||
if meta and meta.display_name then
|
|
||||||
name = meta.display_name
|
|
||||||
end
|
|
||||||
-- First put the unique ID
|
|
||||||
local cols = {}
|
|
||||||
local id_key = cache.format_id(entry[FIELD_ID])
|
|
||||||
col_width[1] = id_key:len()
|
|
||||||
table.insert(cols, id_key)
|
|
||||||
-- Then add all the configured columns
|
|
||||||
for i, column in ipairs(column_defs) do
|
|
||||||
local chunk = columns.render_col(adapter, column, entry)
|
|
||||||
local text = type(chunk) == "table" and chunk[1] or chunk
|
|
||||||
---@cast text string
|
|
||||||
col_width[i + 1] = math.max(col_width[i + 1], vim.api.nvim_strwidth(text))
|
|
||||||
table.insert(cols, chunk)
|
|
||||||
end
|
|
||||||
-- Always add the entry name at the end
|
|
||||||
local entry_type = entry[FIELD_TYPE]
|
|
||||||
if entry_type == "directory" then
|
|
||||||
table.insert(cols, { name .. "/", "OilDir" })
|
|
||||||
elseif entry_type == "socket" then
|
|
||||||
table.insert(cols, { name, "OilSocket" })
|
|
||||||
elseif entry_type == "link" then
|
|
||||||
local link_text
|
local link_text
|
||||||
if meta then
|
if meta then
|
||||||
if meta.link_stat and meta.link_stat.type == "directory" then
|
if meta.link_stat and meta.link_stat.type == "directory" then
|
||||||
|
|
@ -712,20 +744,104 @@ M.format_entry_cols = function(entry, column_defs, col_width, adapter)
|
||||||
end
|
end
|
||||||
|
|
||||||
if meta.link then
|
if meta.link then
|
||||||
link_text = "->" .. " " .. meta.link
|
link_text = "-> " .. meta.link
|
||||||
if meta.link_stat and meta.link_stat.type == "directory" then
|
if meta.link_stat and meta.link_stat.type == "directory" then
|
||||||
link_text = util.addslash(link_text)
|
link_text = util.addslash(link_text)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
table.insert(cols, { name, "OilLink" })
|
return name, link_text
|
||||||
if link_text then
|
end
|
||||||
table.insert(cols, { link_text, "OilLinkTarget" })
|
|
||||||
|
---@private
|
||||||
|
---@param entry oil.InternalEntry
|
||||||
|
---@param column_defs table[]
|
||||||
|
---@param col_width integer[]
|
||||||
|
---@param adapter oil.Adapter
|
||||||
|
---@param is_hidden boolean
|
||||||
|
---@param bufnr integer
|
||||||
|
---@return oil.TextChunk[]
|
||||||
|
M.format_entry_cols = function(entry, column_defs, col_width, adapter, is_hidden, bufnr)
|
||||||
|
local name = entry[FIELD_NAME]
|
||||||
|
local meta = entry[FIELD_META]
|
||||||
|
local hl_suffix = ""
|
||||||
|
if is_hidden then
|
||||||
|
hl_suffix = "Hidden"
|
||||||
|
end
|
||||||
|
if meta and meta.display_name then
|
||||||
|
name = meta.display_name
|
||||||
|
end
|
||||||
|
-- We can't handle newlines in filenames (and shame on you for doing that)
|
||||||
|
name = name:gsub("\n", "")
|
||||||
|
-- First put the unique ID
|
||||||
|
local cols = {}
|
||||||
|
local id_key = cache.format_id(entry[FIELD_ID])
|
||||||
|
col_width[1] = id_key:len()
|
||||||
|
table.insert(cols, id_key)
|
||||||
|
-- Then add all the configured columns
|
||||||
|
for i, column in ipairs(column_defs) do
|
||||||
|
local chunk = columns.render_col(adapter, column, entry, bufnr)
|
||||||
|
local text = type(chunk) == "table" and chunk[1] or chunk
|
||||||
|
---@cast text string
|
||||||
|
col_width[i + 1] = math.max(col_width[i + 1], vim.api.nvim_strwidth(text))
|
||||||
|
table.insert(cols, chunk)
|
||||||
|
end
|
||||||
|
-- Always add the entry name at the end
|
||||||
|
local entry_type = entry[FIELD_TYPE]
|
||||||
|
|
||||||
|
local get_custom_hl = config.view_options.highlight_filename
|
||||||
|
local link_name, link_name_hl, link_target, link_target_hl
|
||||||
|
if get_custom_hl then
|
||||||
|
local external_entry = util.export_entry(entry)
|
||||||
|
|
||||||
|
if entry_type == "link" then
|
||||||
|
link_name, link_target = get_link_text(name, meta)
|
||||||
|
local is_orphan = not (meta and meta.link_stat)
|
||||||
|
link_name_hl = get_custom_hl(external_entry, is_hidden, false, is_orphan, bufnr)
|
||||||
|
|
||||||
|
if link_target then
|
||||||
|
link_target_hl = get_custom_hl(external_entry, is_hidden, true, is_orphan, bufnr)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- intentional fallthrough
|
||||||
|
else
|
||||||
|
local hl = get_custom_hl(external_entry, is_hidden, false, false, bufnr)
|
||||||
|
if hl then
|
||||||
|
-- Add the trailing / if this is a directory, this is important
|
||||||
|
if entry_type == "directory" then
|
||||||
|
name = name .. "/"
|
||||||
|
end
|
||||||
|
table.insert(cols, { name, hl })
|
||||||
|
return cols
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if entry_type == "directory" then
|
||||||
|
table.insert(cols, { name .. "/", "OilDir" .. hl_suffix })
|
||||||
|
elseif entry_type == "socket" then
|
||||||
|
table.insert(cols, { name, "OilSocket" .. hl_suffix })
|
||||||
|
elseif entry_type == "link" then
|
||||||
|
if not link_name then
|
||||||
|
link_name, link_target = get_link_text(name, meta)
|
||||||
|
end
|
||||||
|
local is_orphan = not (meta and meta.link_stat)
|
||||||
|
if not link_name_hl then
|
||||||
|
link_name_hl = (is_orphan and "OilOrphanLink" or "OilLink") .. hl_suffix
|
||||||
|
end
|
||||||
|
table.insert(cols, { link_name, link_name_hl })
|
||||||
|
|
||||||
|
if link_target then
|
||||||
|
if not link_target_hl then
|
||||||
|
link_target_hl = (is_orphan and "OilOrphanLinkTarget" or "OilLinkTarget") .. hl_suffix
|
||||||
|
end
|
||||||
|
table.insert(cols, { link_target, link_target_hl })
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
table.insert(cols, { name, "OilFile" })
|
table.insert(cols, { name, "OilFile" .. hl_suffix })
|
||||||
end
|
end
|
||||||
|
|
||||||
return cols
|
return cols
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -795,7 +911,7 @@ M.render_buffer_async = function(bufnr, opts, callback)
|
||||||
handle_error(string.format("Could not parse oil url '%s'", bufname))
|
handle_error(string.format("Could not parse oil url '%s'", bufname))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local adapter = util.get_adapter(bufnr)
|
local adapter = util.get_adapter(bufnr, true)
|
||||||
if not adapter then
|
if not adapter then
|
||||||
handle_error(string.format("[oil] no adapter for buffer '%s'", bufname))
|
handle_error(string.format("[oil] no adapter for buffer '%s'", bufname))
|
||||||
return
|
return
|
||||||
|
|
@ -814,6 +930,7 @@ M.render_buffer_async = function(bufnr, opts, callback)
|
||||||
vim.b[bufnr].oil_rendering = false
|
vim.b[bufnr].oil_rendering = false
|
||||||
loading.set_loading(bufnr, false)
|
loading.set_loading(bufnr, false)
|
||||||
render_buffer(bufnr, { jump = true })
|
render_buffer(bufnr, { jump = true })
|
||||||
|
M.set_last_cursor(bufname, nil)
|
||||||
vim.bo[bufnr].undolevels = vim.api.nvim_get_option_value("undolevels", { scope = "global" })
|
vim.bo[bufnr].undolevels = vim.api.nvim_get_option_value("undolevels", { scope = "global" })
|
||||||
vim.bo[bufnr].modifiable = not buffers_locked and adapter.is_modifiable(bufnr)
|
vim.bo[bufnr].modifiable = not buffers_locked and adapter.is_modifiable(bufnr)
|
||||||
if callback then
|
if callback then
|
||||||
|
|
@ -838,6 +955,7 @@ M.render_buffer_async = function(bufnr, opts, callback)
|
||||||
end
|
end
|
||||||
|
|
||||||
cache.begin_update_url(bufname)
|
cache.begin_update_url(bufname)
|
||||||
|
local num_iterations = 0
|
||||||
adapter.list(bufname, get_used_columns(), function(err, entries, fetch_more)
|
adapter.list(bufname, get_used_columns(), function(err, entries, fetch_more)
|
||||||
loading.set_loading(bufnr, false)
|
loading.set_loading(bufnr, false)
|
||||||
if err then
|
if err then
|
||||||
|
|
@ -854,11 +972,13 @@ M.render_buffer_async = function(bufnr, opts, callback)
|
||||||
local now = uv.hrtime() / 1e6
|
local now = uv.hrtime() / 1e6
|
||||||
local delta = now - start_ms
|
local delta = now - start_ms
|
||||||
-- If we've been chugging for more than 40ms, go ahead and render what we have
|
-- If we've been chugging for more than 40ms, go ahead and render what we have
|
||||||
if delta > 40 then
|
if (delta > 25 and num_iterations < 1) or delta > 500 then
|
||||||
|
num_iterations = num_iterations + 1
|
||||||
start_ms = now
|
start_ms = now
|
||||||
vim.schedule(function()
|
vim.schedule(function()
|
||||||
seek_after_render_found =
|
seek_after_render_found =
|
||||||
render_buffer(bufnr, { jump = not seek_after_render_found, jump_first = first })
|
render_buffer(bufnr, { jump = not seek_after_render_found, jump_first = first })
|
||||||
|
start_ms = uv.hrtime() / 1e6
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
first = false
|
first = false
|
||||||
|
|
|
||||||
63
perf/bootstrap.lua
Normal file
63
perf/bootstrap.lua
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
vim.opt.runtimepath:prepend("scripts/benchmark.nvim")
|
||||||
|
vim.opt.runtimepath:prepend(".")
|
||||||
|
|
||||||
|
local bm = require("benchmark")
|
||||||
|
bm.sandbox()
|
||||||
|
|
||||||
|
---@module 'oil'
|
||||||
|
---@type oil.SetupOpts
|
||||||
|
local setup_opts = {
|
||||||
|
-- columns = { "icon", "permissions", "size", "mtime" },
|
||||||
|
}
|
||||||
|
|
||||||
|
local DIR_SIZE = tonumber(vim.env.DIR_SIZE) or 100000
|
||||||
|
local ITERATIONS = tonumber(vim.env.ITERATIONS) or 10
|
||||||
|
local WARM_UP = tonumber(vim.env.WARM_UP) or 1
|
||||||
|
local OUTLIERS = tonumber(vim.env.OUTLIERS) or math.floor(ITERATIONS / 10)
|
||||||
|
local TEST_DIR = "perf/tmp/test_" .. DIR_SIZE
|
||||||
|
|
||||||
|
vim.fn.mkdir(TEST_DIR, "p")
|
||||||
|
require("benchmark.files").create_files(TEST_DIR, "file %d.txt", DIR_SIZE)
|
||||||
|
|
||||||
|
function _G.jit_profile()
|
||||||
|
require("oil").setup(setup_opts)
|
||||||
|
local finish = bm.jit_profile({ filename = TEST_DIR .. "/profile.txt" })
|
||||||
|
bm.wait_for_user_event("OilEnter", function()
|
||||||
|
finish()
|
||||||
|
end)
|
||||||
|
require("oil").open(TEST_DIR)
|
||||||
|
end
|
||||||
|
|
||||||
|
function _G.flame_profile()
|
||||||
|
local start, stop = bm.flame_profile({
|
||||||
|
pattern = "oil*",
|
||||||
|
filename = "profile.json",
|
||||||
|
})
|
||||||
|
require("oil").setup(setup_opts)
|
||||||
|
start()
|
||||||
|
bm.wait_for_user_event("OilEnter", function()
|
||||||
|
stop(function()
|
||||||
|
vim.cmd.qall({ mods = { silent = true } })
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
require("oil").open(TEST_DIR)
|
||||||
|
end
|
||||||
|
|
||||||
|
function _G.benchmark()
|
||||||
|
require("oil").setup(setup_opts)
|
||||||
|
bm.run({ title = "oil.nvim", iterations = ITERATIONS, warm_up = WARM_UP }, function(callback)
|
||||||
|
bm.wait_for_user_event("OilEnter", callback)
|
||||||
|
require("oil").open(TEST_DIR)
|
||||||
|
end, function(times)
|
||||||
|
local avg = bm.avg(times, { trim_outliers = OUTLIERS })
|
||||||
|
local std_dev = bm.std_dev(times, { trim_outliers = OUTLIERS })
|
||||||
|
local lines = {
|
||||||
|
table.concat(vim.tbl_map(bm.format_time, times), " "),
|
||||||
|
string.format("Average: %s", bm.format_time(avg)),
|
||||||
|
string.format("Std deviation: %s", bm.format_time(std_dev)),
|
||||||
|
}
|
||||||
|
|
||||||
|
vim.fn.writefile(lines, "perf/tmp/benchmark.txt")
|
||||||
|
vim.cmd.qall({ mods = { silent = true } })
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
mkdir -p ".testenv/config/nvim"
|
mkdir -p ".testenv/config/nvim"
|
||||||
|
|
|
||||||
|
|
@ -110,11 +110,16 @@ class ColumnDef:
|
||||||
params: List["LuaParam"] = field(default_factory=list)
|
params: List["LuaParam"] = field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
HL = [
|
UNIVERSAL = [
|
||||||
LuaParam(
|
LuaParam(
|
||||||
"highlight",
|
"highlight",
|
||||||
"string|fun(value: string): string",
|
"string|fun(value: string): string",
|
||||||
"Highlight group, or function that returns a highlight group",
|
"Highlight group, or function that returns a highlight group",
|
||||||
|
),
|
||||||
|
LuaParam(
|
||||||
|
"align",
|
||||||
|
'"left"|"center"|"right"',
|
||||||
|
"Text alignment within the column",
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
TIME = [
|
TIME = [
|
||||||
|
|
@ -127,7 +132,7 @@ COL_DEFS = [
|
||||||
False,
|
False,
|
||||||
True,
|
True,
|
||||||
"The type of the entry (file, directory, link, etc)",
|
"The type of the entry (file, directory, link, etc)",
|
||||||
HL
|
UNIVERSAL
|
||||||
+ [LuaParam("icons", "table<string, string>", "Mapping of entry type to icon")],
|
+ [LuaParam("icons", "table<string, string>", "Mapping of entry type to icon")],
|
||||||
),
|
),
|
||||||
ColumnDef(
|
ColumnDef(
|
||||||
|
|
@ -136,7 +141,7 @@ COL_DEFS = [
|
||||||
False,
|
False,
|
||||||
False,
|
False,
|
||||||
"An icon for the entry's type (requires nvim-web-devicons)",
|
"An icon for the entry's type (requires nvim-web-devicons)",
|
||||||
HL
|
UNIVERSAL
|
||||||
+ [
|
+ [
|
||||||
LuaParam(
|
LuaParam(
|
||||||
"default_file",
|
"default_file",
|
||||||
|
|
@ -151,31 +156,31 @@ COL_DEFS = [
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
ColumnDef("size", "files, ssh", False, True, "The size of the file", HL + []),
|
ColumnDef("size", "files, ssh, s3", False, True, "The size of the file", UNIVERSAL + []),
|
||||||
ColumnDef(
|
ColumnDef(
|
||||||
"permissions",
|
"permissions",
|
||||||
"files, ssh",
|
"files, ssh",
|
||||||
True,
|
True,
|
||||||
False,
|
False,
|
||||||
"Access permissions of the file",
|
"Access permissions of the file",
|
||||||
HL + [],
|
UNIVERSAL + [],
|
||||||
),
|
),
|
||||||
ColumnDef(
|
ColumnDef(
|
||||||
"ctime", "files", False, True, "Change timestamp of the file", HL + TIME + []
|
"ctime", "files", False, True, "Change timestamp of the file", UNIVERSAL + TIME + []
|
||||||
),
|
),
|
||||||
ColumnDef(
|
ColumnDef(
|
||||||
"mtime", "files", False, True, "Last modified time of the file", HL + TIME + []
|
"mtime", "files", False, True, "Last modified time of the file", UNIVERSAL + TIME + []
|
||||||
),
|
),
|
||||||
ColumnDef(
|
ColumnDef(
|
||||||
"atime", "files", False, True, "Last access time of the file", HL + TIME + []
|
"atime", "files", False, True, "Last access time of the file", UNIVERSAL + TIME + []
|
||||||
),
|
),
|
||||||
ColumnDef(
|
ColumnDef(
|
||||||
"birthtime",
|
"birthtime",
|
||||||
"files",
|
"files, s3",
|
||||||
False,
|
False,
|
||||||
True,
|
True,
|
||||||
"The time the file was created",
|
"The time the file was created",
|
||||||
HL + TIME + [],
|
UNIVERSAL + TIME + [],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -351,9 +356,9 @@ of being permanently deleted. You can browse the trash for a directory using
|
||||||
the `toggle_trash` action (bound to `g\\` by default). You can view all files
|
the `toggle_trash` action (bound to `g\\` by default). You can view all files
|
||||||
in the trash with `:Oil --trash /`.
|
in the trash with `:Oil --trash /`.
|
||||||
|
|
||||||
To restore files, simply delete them from the trash and put them in the desired
|
To restore files, simply move them from the trash to the desired destination,
|
||||||
destination, the same as any other file operation. If you delete files from the
|
the same as any other file operation. If you delete files from the trash they
|
||||||
trash they will be permanently deleted (purged).
|
will be permanently deleted (purged).
|
||||||
|
|
||||||
Linux:
|
Linux:
|
||||||
Oil supports the FreeDesktop trash specification.
|
Oil supports the FreeDesktop trash specification.
|
||||||
|
|
@ -366,7 +371,7 @@ Mac:
|
||||||
(instead of being able to see files that were trashed from a directory).
|
(instead of being able to see files that were trashed from a directory).
|
||||||
|
|
||||||
Windows:
|
Windows:
|
||||||
Oil does not yet support the Windows trash. PRs are welcome!
|
Oil supports the Windows Recycle Bin. All features should work.
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
return section
|
return section
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ if exists("b:current_syntax")
|
||||||
finish
|
finish
|
||||||
endif
|
endif
|
||||||
|
|
||||||
syn match oilCreate /^CREATE /
|
syn match oilCreate /^CREATE\( BUCKET\)\? /
|
||||||
syn match oilMove /^ MOVE /
|
syn match oilMove /^ MOVE /
|
||||||
syn match oilDelete /^DELETE /
|
syn match oilDelete /^DELETE\( BUCKET\)\? /
|
||||||
syn match oilCopy /^ COPY /
|
syn match oilCopy /^ COPY /
|
||||||
syn match oilChange /^CHANGE /
|
syn match oilChange /^CHANGE /
|
||||||
" Trash operations
|
" Trash operations
|
||||||
|
|
|
||||||
|
|
@ -141,5 +141,17 @@ a.describe("Alternate buffer", function()
|
||||||
oil.close()
|
oil.close()
|
||||||
assert.equals("foo", vim.fn.expand("#"))
|
assert.equals("foo", vim.fn.expand("#"))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
a.it("preserves alternate when traversing to a new file", function()
|
||||||
|
vim.cmd.edit({ args = { "foo" } })
|
||||||
|
oil.open_float()
|
||||||
|
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
||||||
|
assert.equals("foo", vim.fn.expand("#"))
|
||||||
|
test_util.feedkeys({ "/LICENSE<CR>" }, 10)
|
||||||
|
oil.select()
|
||||||
|
test_util.wait_for_autocmd("BufEnter")
|
||||||
|
assert.equals("LICENSE", vim.fn.expand("%:."))
|
||||||
|
assert.equals("foo", vim.fn.expand("#"))
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
|
||||||
|
|
@ -150,10 +150,10 @@ a.describe("files adapter", function()
|
||||||
a.it("Editing a new oil://path/ creates an oil buffer", function()
|
a.it("Editing a new oil://path/ creates an oil buffer", function()
|
||||||
local tmpdir_url = "oil://" .. vim.fn.fnamemodify(tmpdir.path, ":p") .. "/"
|
local tmpdir_url = "oil://" .. vim.fn.fnamemodify(tmpdir.path, ":p") .. "/"
|
||||||
vim.cmd.edit({ args = { tmpdir_url } })
|
vim.cmd.edit({ args = { tmpdir_url } })
|
||||||
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
test_util.wait_oil_ready()
|
||||||
local new_url = "oil://" .. vim.fn.fnamemodify(tmpdir.path, ":p") .. "newdir"
|
local new_url = "oil://" .. vim.fn.fnamemodify(tmpdir.path, ":p") .. "newdir"
|
||||||
vim.cmd.edit({ args = { new_url } })
|
vim.cmd.edit({ args = { new_url } })
|
||||||
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
test_util.wait_oil_ready()
|
||||||
assert.equals("oil", vim.bo.filetype)
|
assert.equals("oil", vim.bo.filetype)
|
||||||
-- The normalization will add a '/'
|
-- The normalization will add a '/'
|
||||||
assert.equals(new_url .. "/", vim.api.nvim_buf_get_name(0))
|
assert.equals(new_url .. "/", vim.api.nvim_buf_get_name(0))
|
||||||
|
|
@ -168,5 +168,6 @@ a.describe("files adapter", function()
|
||||||
test_util.wait_for_autocmd("BufReadPost")
|
test_util.wait_for_autocmd("BufReadPost")
|
||||||
assert.equals("ruby", vim.bo.filetype)
|
assert.equals("ruby", vim.bo.filetype)
|
||||||
assert.equals(vim.fn.fnamemodify(tmpdir.path, ":p") .. "file.rb", vim.api.nvim_buf_get_name(0))
|
assert.equals(vim.fn.fnamemodify(tmpdir.path, ":p") .. "file.rb", vim.api.nvim_buf_get_name(0))
|
||||||
|
assert.equals(tmpdir.path .. "/file.rb", vim.fn.bufname())
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ describe("parser", function()
|
||||||
local file = test_adapter.test_set("/foo/a.txt", "file")
|
local file = test_adapter.test_set("/foo/a.txt", "file")
|
||||||
vim.cmd.edit({ args = { "oil-test:///foo/" } })
|
vim.cmd.edit({ args = { "oil-test:///foo/" } })
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
local bufnr = vim.api.nvim_get_current_buf()
|
||||||
local cols = view.format_entry_cols(file, {}, {}, test_adapter)
|
local cols = view.format_entry_cols(file, {}, {}, test_adapter, false)
|
||||||
local lines = util.render_table({ cols }, {})
|
local lines = util.render_table({ cols }, {})
|
||||||
table.insert(lines, "")
|
table.insert(lines, "")
|
||||||
table.insert(lines, " ")
|
table.insert(lines, " ")
|
||||||
|
|
|
||||||
41
tests/preview_spec.lua
Normal file
41
tests/preview_spec.lua
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
require("plenary.async").tests.add_to_env()
|
||||||
|
local TmpDir = require("tests.tmpdir")
|
||||||
|
local oil = require("oil")
|
||||||
|
local test_util = require("tests.test_util")
|
||||||
|
local util = require("oil.util")
|
||||||
|
|
||||||
|
a.describe("oil preview", function()
|
||||||
|
local tmpdir
|
||||||
|
a.before_each(function()
|
||||||
|
tmpdir = TmpDir.new()
|
||||||
|
end)
|
||||||
|
a.after_each(function()
|
||||||
|
if tmpdir then
|
||||||
|
tmpdir:dispose()
|
||||||
|
end
|
||||||
|
test_util.reset_editor()
|
||||||
|
end)
|
||||||
|
|
||||||
|
a.it("opens preview window", function()
|
||||||
|
tmpdir:create({ "a.txt" })
|
||||||
|
test_util.oil_open(tmpdir.path)
|
||||||
|
a.wrap(oil.open_preview, 2)()
|
||||||
|
local preview_win = util.get_preview_win()
|
||||||
|
assert.not_nil(preview_win)
|
||||||
|
assert(preview_win)
|
||||||
|
local bufnr = vim.api.nvim_win_get_buf(preview_win)
|
||||||
|
local preview_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
||||||
|
assert.are.same({ "a.txt" }, preview_lines)
|
||||||
|
end)
|
||||||
|
|
||||||
|
a.it("opens preview window when open(preview={})", function()
|
||||||
|
tmpdir:create({ "a.txt" })
|
||||||
|
test_util.oil_open(tmpdir.path, { preview = {} })
|
||||||
|
local preview_win = util.get_preview_win()
|
||||||
|
assert.not_nil(preview_win)
|
||||||
|
assert(preview_win)
|
||||||
|
local bufnr = vim.api.nvim_win_get_buf(preview_win)
|
||||||
|
local preview_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
||||||
|
assert.are.same({ "a.txt" }, preview_lines)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
@ -8,8 +8,7 @@ a.describe("oil select", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
a.it("opens file under cursor", function()
|
a.it("opens file under cursor", function()
|
||||||
oil.open()
|
test_util.oil_open()
|
||||||
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
|
||||||
-- Go to the bottom, so the cursor is not on a directory
|
-- Go to the bottom, so the cursor is not on a directory
|
||||||
vim.cmd.normal({ args = { "G" } })
|
vim.cmd.normal({ args = { "G" } })
|
||||||
a.wrap(oil.select, 2)()
|
a.wrap(oil.select, 2)()
|
||||||
|
|
@ -18,8 +17,7 @@ a.describe("oil select", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
a.it("opens file in new tab", function()
|
a.it("opens file in new tab", function()
|
||||||
oil.open()
|
test_util.oil_open()
|
||||||
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
|
||||||
local tabpage = vim.api.nvim_get_current_tabpage()
|
local tabpage = vim.api.nvim_get_current_tabpage()
|
||||||
a.wrap(oil.select, 2)({ tab = true })
|
a.wrap(oil.select, 2)({ tab = true })
|
||||||
assert.equals(2, #vim.api.nvim_list_tabpages())
|
assert.equals(2, #vim.api.nvim_list_tabpages())
|
||||||
|
|
@ -28,8 +26,7 @@ a.describe("oil select", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
a.it("opens file in new split", function()
|
a.it("opens file in new split", function()
|
||||||
oil.open()
|
test_util.oil_open()
|
||||||
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
|
||||||
local winid = vim.api.nvim_get_current_win()
|
local winid = vim.api.nvim_get_current_win()
|
||||||
a.wrap(oil.select, 2)({ vertical = true })
|
a.wrap(oil.select, 2)({ vertical = true })
|
||||||
assert.equals(1, #vim.api.nvim_list_tabpages())
|
assert.equals(1, #vim.api.nvim_list_tabpages())
|
||||||
|
|
@ -38,8 +35,7 @@ a.describe("oil select", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
a.it("opens multiple files in new tabs", function()
|
a.it("opens multiple files in new tabs", function()
|
||||||
oil.open()
|
test_util.oil_open()
|
||||||
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
|
||||||
vim.api.nvim_feedkeys("Vj", "x", true)
|
vim.api.nvim_feedkeys("Vj", "x", true)
|
||||||
local tabpage = vim.api.nvim_get_current_tabpage()
|
local tabpage = vim.api.nvim_get_current_tabpage()
|
||||||
a.wrap(oil.select, 2)({ tab = true })
|
a.wrap(oil.select, 2)({ tab = true })
|
||||||
|
|
@ -49,8 +45,7 @@ a.describe("oil select", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
a.it("opens multiple files in new splits", function()
|
a.it("opens multiple files in new splits", function()
|
||||||
oil.open()
|
test_util.oil_open()
|
||||||
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
|
||||||
vim.api.nvim_feedkeys("Vj", "x", true)
|
vim.api.nvim_feedkeys("Vj", "x", true)
|
||||||
local winid = vim.api.nvim_get_current_win()
|
local winid = vim.api.nvim_get_current_win()
|
||||||
a.wrap(oil.select, 2)({ vertical = true })
|
a.wrap(oil.select, 2)({ vertical = true })
|
||||||
|
|
@ -63,8 +58,7 @@ a.describe("oil select", function()
|
||||||
a.it("same window", function()
|
a.it("same window", function()
|
||||||
vim.cmd.edit({ args = { "foo" } })
|
vim.cmd.edit({ args = { "foo" } })
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
local bufnr = vim.api.nvim_get_current_buf()
|
||||||
oil.open()
|
test_util.oil_open()
|
||||||
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
|
||||||
-- Go to the bottom, so the cursor is not on a directory
|
-- Go to the bottom, so the cursor is not on a directory
|
||||||
vim.cmd.normal({ args = { "G" } })
|
vim.cmd.normal({ args = { "G" } })
|
||||||
a.wrap(oil.select, 2)({ close = true })
|
a.wrap(oil.select, 2)({ close = true })
|
||||||
|
|
@ -79,8 +73,7 @@ a.describe("oil select", function()
|
||||||
vim.cmd.edit({ args = { "foo" } })
|
vim.cmd.edit({ args = { "foo" } })
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
local bufnr = vim.api.nvim_get_current_buf()
|
||||||
local winid = vim.api.nvim_get_current_win()
|
local winid = vim.api.nvim_get_current_win()
|
||||||
oil.open()
|
test_util.oil_open()
|
||||||
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
|
||||||
a.wrap(oil.select, 2)({ vertical = true, close = true })
|
a.wrap(oil.select, 2)({ vertical = true, close = true })
|
||||||
assert.equals(2, #vim.api.nvim_tabpage_list_wins(0))
|
assert.equals(2, #vim.api.nvim_tabpage_list_wins(0))
|
||||||
assert.equals(bufnr, vim.api.nvim_win_get_buf(winid))
|
assert.equals(bufnr, vim.api.nvim_win_get_buf(winid))
|
||||||
|
|
@ -90,8 +83,7 @@ a.describe("oil select", function()
|
||||||
vim.cmd.edit({ args = { "foo" } })
|
vim.cmd.edit({ args = { "foo" } })
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
local bufnr = vim.api.nvim_get_current_buf()
|
||||||
local tabpage = vim.api.nvim_get_current_tabpage()
|
local tabpage = vim.api.nvim_get_current_tabpage()
|
||||||
oil.open()
|
test_util.oil_open()
|
||||||
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
|
||||||
a.wrap(oil.select, 2)({ tab = true, close = true })
|
a.wrap(oil.select, 2)({ tab = true, close = true })
|
||||||
assert.equals(1, #vim.api.nvim_tabpage_list_wins(0))
|
assert.equals(1, #vim.api.nvim_tabpage_list_wins(0))
|
||||||
assert.equals(2, #vim.api.nvim_list_tabpages())
|
assert.equals(2, #vim.api.nvim_list_tabpages())
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
require("plenary.async").tests.add_to_env()
|
require("plenary.async").tests.add_to_env()
|
||||||
local cache = require("oil.cache")
|
local cache = require("oil.cache")
|
||||||
local test_adapter = require("oil.adapters.test")
|
local test_adapter = require("oil.adapters.test")
|
||||||
|
local util = require("oil.util")
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
M.reset_editor = function()
|
M.reset_editor = function()
|
||||||
|
|
@ -33,6 +34,10 @@ local function throwiferr(err, ...)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
M.oil_open = function(...)
|
||||||
|
a.wrap(require("oil").open, 3)(...)
|
||||||
|
end
|
||||||
|
|
||||||
M.await = function(fn, nargs, ...)
|
M.await = function(fn, nargs, ...)
|
||||||
return throwiferr(a.wrap(fn, nargs)(...))
|
return throwiferr(a.wrap(fn, nargs)(...))
|
||||||
end
|
end
|
||||||
|
|
@ -53,6 +58,10 @@ M.wait_for_autocmd = a.wrap(function(autocmd, cb)
|
||||||
vim.api.nvim_create_autocmd(autocmd, opts)
|
vim.api.nvim_create_autocmd(autocmd, opts)
|
||||||
end, 2)
|
end, 2)
|
||||||
|
|
||||||
|
M.wait_oil_ready = a.wrap(function(cb)
|
||||||
|
util.run_after_load(0, vim.schedule_wrap(cb))
|
||||||
|
end, 1)
|
||||||
|
|
||||||
---@param actions string[]
|
---@param actions string[]
|
||||||
---@param timestep integer
|
---@param timestep integer
|
||||||
M.feedkeys = function(actions, timestep)
|
M.feedkeys = function(actions, timestep)
|
||||||
|
|
|
||||||
29
tests/util_spec.lua
Normal file
29
tests/util_spec.lua
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
local util = require("oil.util")
|
||||||
|
describe("util", function()
|
||||||
|
it("url_escape", function()
|
||||||
|
local cases = {
|
||||||
|
{ "foobar", "foobar" },
|
||||||
|
{ "foo bar", "foo%20bar" },
|
||||||
|
{ "/foo/bar", "%2Ffoo%2Fbar" },
|
||||||
|
}
|
||||||
|
for _, case in ipairs(cases) do
|
||||||
|
local input, expected = unpack(case)
|
||||||
|
local output = util.url_escape(input)
|
||||||
|
assert.equals(expected, output)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("url_unescape", function()
|
||||||
|
local cases = {
|
||||||
|
{ "foobar", "foobar" },
|
||||||
|
{ "foo%20bar", "foo bar" },
|
||||||
|
{ "%2Ffoo%2Fbar", "/foo/bar" },
|
||||||
|
{ "foo%%bar", "foo%%bar" },
|
||||||
|
}
|
||||||
|
for _, case in ipairs(cases) do
|
||||||
|
local input, expected = unpack(case)
|
||||||
|
local output = util.url_unescape(input)
|
||||||
|
assert.equals(expected, output)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
@ -9,32 +9,28 @@ a.describe("window options", function()
|
||||||
|
|
||||||
a.it("Restores window options on close", function()
|
a.it("Restores window options on close", function()
|
||||||
vim.cmd.edit({ args = { "README.md" } })
|
vim.cmd.edit({ args = { "README.md" } })
|
||||||
oil.open()
|
test_util.oil_open()
|
||||||
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
|
||||||
assert.equals("no", vim.o.signcolumn)
|
assert.equals("no", vim.o.signcolumn)
|
||||||
oil.close()
|
oil.close()
|
||||||
assert.equals("auto", vim.o.signcolumn)
|
assert.equals("auto", vim.o.signcolumn)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
a.it("Restores window options on edit", function()
|
a.it("Restores window options on edit", function()
|
||||||
oil.open()
|
test_util.oil_open()
|
||||||
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
|
||||||
assert.equals("no", vim.o.signcolumn)
|
assert.equals("no", vim.o.signcolumn)
|
||||||
vim.cmd.edit({ args = { "README.md" } })
|
vim.cmd.edit({ args = { "README.md" } })
|
||||||
assert.equals("auto", vim.o.signcolumn)
|
assert.equals("auto", vim.o.signcolumn)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
a.it("Restores window options on split <filename>", function()
|
a.it("Restores window options on split <filename>", function()
|
||||||
oil.open()
|
test_util.oil_open()
|
||||||
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
|
||||||
assert.equals("no", vim.o.signcolumn)
|
assert.equals("no", vim.o.signcolumn)
|
||||||
vim.cmd.split({ args = { "README.md" } })
|
vim.cmd.split({ args = { "README.md" } })
|
||||||
assert.equals("auto", vim.o.signcolumn)
|
assert.equals("auto", vim.o.signcolumn)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
a.it("Restores window options on split", function()
|
a.it("Restores window options on split", function()
|
||||||
oil.open()
|
test_util.oil_open()
|
||||||
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
|
||||||
assert.equals("no", vim.o.signcolumn)
|
assert.equals("no", vim.o.signcolumn)
|
||||||
vim.cmd.split()
|
vim.cmd.split()
|
||||||
vim.cmd.edit({ args = { "README.md" } })
|
vim.cmd.edit({ args = { "README.md" } })
|
||||||
|
|
@ -42,16 +38,14 @@ a.describe("window options", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
a.it("Restores window options on tabnew <filename>", function()
|
a.it("Restores window options on tabnew <filename>", function()
|
||||||
oil.open()
|
test_util.oil_open()
|
||||||
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
|
||||||
assert.equals("no", vim.o.signcolumn)
|
assert.equals("no", vim.o.signcolumn)
|
||||||
vim.cmd.tabnew({ args = { "README.md" } })
|
vim.cmd.tabnew({ args = { "README.md" } })
|
||||||
assert.equals("auto", vim.o.signcolumn)
|
assert.equals("auto", vim.o.signcolumn)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
a.it("Restores window options on tabnew", function()
|
a.it("Restores window options on tabnew", function()
|
||||||
oil.open()
|
test_util.oil_open()
|
||||||
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
|
||||||
assert.equals("no", vim.o.signcolumn)
|
assert.equals("no", vim.o.signcolumn)
|
||||||
vim.cmd.tabnew()
|
vim.cmd.tabnew()
|
||||||
vim.cmd.edit({ args = { "README.md" } })
|
vim.cmd.edit({ args = { "README.md" } })
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue