mirror of
https://github.com/harivansh-afk/oil.nvim.git
synced 2026-04-15 23:01:36 +00:00
Compare commits
206 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 | ||
|
|
50c4bd4ee2 | ||
|
|
6e754e6699 | ||
|
|
3499e26ef4 | ||
|
|
2f5d4353ee | ||
|
|
eb5497f0ac | ||
|
|
1f5b002270 | ||
|
|
621f8ba4fa | ||
|
|
709403ccd6 | ||
|
|
52cc8a1fb3 | ||
|
|
42333bb46e | ||
|
|
cca1631d5e | ||
|
|
28aca0c1f5 | ||
|
|
39dbf87586 | ||
|
|
5d2dfae655 | ||
|
|
ccab9d5e09 | ||
|
|
9e6fb844fe | ||
|
|
581c729805 | ||
|
|
1360be5fda | ||
|
|
f60bb7f793 | ||
|
|
665bf2edc9 | ||
|
|
eadc3ed42e | ||
|
|
1eb9fb35a4 | ||
|
|
b05374428e | ||
|
|
1fe476daf0 | ||
|
|
d10e7f442f | ||
|
|
0dc98d36b5 | ||
|
|
85637c1e63 | ||
|
|
30e0438ff0 | ||
|
|
0fcd1263a2 | ||
|
|
4f3c6780ff | ||
|
|
70337eb77f | ||
|
|
349bca8c3e | ||
|
|
a632c898fb | ||
|
|
b39a78959f | ||
|
|
fcca212c2e | ||
|
|
71c972fbd2 | ||
|
|
a6cea1a5b9 | ||
|
|
9e5eb2fcd1 | ||
|
|
10fbfdd37b | ||
|
|
cc2332599f | ||
|
|
d5e56574f8 | ||
|
|
a543ea598e | ||
|
|
b5a1abfde0 | ||
|
|
b15e4c1e64 | ||
|
|
b0a6cf9898 | ||
|
|
ace46a41a1 | ||
|
|
2077cc3358 | ||
|
|
c7c7ce5bd4 | ||
|
|
65c53dbe4f | ||
|
|
f6df58ad37 | ||
|
|
4c574cf4a2 | ||
|
|
59b3dab6f7 | ||
|
|
0883b109a7 | ||
|
|
64a3a555b4 | ||
|
|
b77ed915ab | ||
|
|
ca8b62fca5 | ||
|
|
c82b26eb4b | ||
|
|
76bfc25520 | ||
|
|
61f1967222 | ||
|
|
e5eb20e88f | ||
|
|
a62ec258d1 | ||
|
|
96368e13e9 | ||
|
|
18272aba9d | ||
|
|
e5312c3a80 | ||
|
|
bbc0e67eeb | ||
|
|
d3a365c950 | ||
|
|
15e071f203 | ||
|
|
8ac4ba4e0a | ||
|
|
2cb39e838e | ||
|
|
259b1fbc84 | ||
|
|
06a19f77f1 | ||
|
|
9e3a02252d | ||
|
|
6f452e8d47 | ||
|
|
80eb2d6719 | ||
|
|
27d9f37161 | ||
|
|
8a2de6ada2 | ||
|
|
f630887cd8 | ||
|
|
3283deec96 | ||
|
|
aa0c00c7fd | ||
|
|
010b44a79d | ||
|
|
752563c59d | ||
|
|
3abb6077d7 | ||
|
|
bcfc0a2e01 | ||
|
|
f3a31eba24 | ||
|
|
3b3a6b23a1 | ||
|
|
96f0983e75 | ||
|
|
be0a1ecbf0 | ||
|
|
6a7a10b611 | ||
|
|
2edb43a7ec | ||
|
|
f41a0f24c0 | ||
|
|
a3c03e442a | ||
|
|
2bc56ad68a | ||
|
|
1f05774e1c | ||
|
|
354c53080a | ||
|
|
c86e48407b | ||
|
|
f41d7e7cd8 | ||
|
|
fa3820ebf1 |
65 changed files with 4817 additions and 1414 deletions
2
.envrc
2
.envrc
|
|
@ -1 +1,3 @@
|
||||||
|
export VIRTUAL_ENV=venv
|
||||||
layout python
|
layout python
|
||||||
|
python -c 'import pyparsing' 2>/dev/null || pip install -r scripts/requirements.txt
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.event.sender.login != 'stevearc'
|
if: github.event.sender.login != 'stevearc'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- uses: actions-ecosystem/action-remove-labels@v1
|
- uses: actions-ecosystem/action-remove-labels@v1
|
||||||
with:
|
with:
|
||||||
labels: question
|
labels: question
|
||||||
|
|
|
||||||
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
|
||||||
|
|
|
||||||
24
.github/workflows/tests.yml
vendored
24
.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
|
||||||
|
|
@ -13,7 +14,7 @@ jobs:
|
||||||
name: Luacheck
|
name: Luacheck
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Prepare
|
- name: Prepare
|
||||||
run: |
|
run: |
|
||||||
|
|
@ -29,20 +30,20 @@ jobs:
|
||||||
name: StyLua
|
name: StyLua
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Stylua
|
- name: Stylua
|
||||||
uses: JohnnyMorganz/stylua-action@v3
|
uses: JohnnyMorganz/stylua-action@v4
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
version: v0.18.2
|
version: v2.0.2
|
||||||
args: --check lua tests
|
args: --check lua tests
|
||||||
|
|
||||||
typecheck:
|
typecheck:
|
||||||
name: typecheck
|
name: typecheck
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: stevearc/nvim-typecheck-action@v1
|
- uses: stevearc/nvim-typecheck-action@v2
|
||||||
with:
|
with:
|
||||||
path: lua
|
path: lua
|
||||||
|
|
||||||
|
|
@ -52,13 +53,15 @@ 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.4
|
||||||
|
- nvim_tag: v0.11.0
|
||||||
|
|
||||||
name: Run tests
|
name: Run tests
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
env:
|
env:
|
||||||
NVIM_TAG: ${{ matrix.nvim_tag }}
|
NVIM_TAG: ${{ matrix.nvim_tag }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Neovim and dependencies
|
- name: Install Neovim and dependencies
|
||||||
run: |
|
run: |
|
||||||
|
|
@ -72,7 +75,7 @@ jobs:
|
||||||
name: Update docs
|
name: Update docs
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Neovim and dependencies
|
- name: Install Neovim and dependencies
|
||||||
run: |
|
run: |
|
||||||
|
|
@ -109,12 +112,11 @@ jobs:
|
||||||
- update_docs
|
- update_docs
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: google-github-actions/release-please-action@v3
|
- uses: googleapis/release-please-action@v4
|
||||||
id: release
|
id: release
|
||||||
with:
|
with:
|
||||||
release-type: simple
|
release-type: simple
|
||||||
package-name: oil.nvim
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: rickstaa/action-create-tag@v1
|
- uses: rickstaa/action-create-tag@v1
|
||||||
if: ${{ steps.release.outputs.release_created }}
|
if: ${{ steps.release.outputs.release_created }}
|
||||||
with:
|
with:
|
||||||
|
|
|
||||||
7
.gitignore
vendored
7
.gitignore
vendored
|
|
@ -6,6 +6,9 @@ luac.out
|
||||||
*.zip
|
*.zip
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
|
|
||||||
|
# python bytecode
|
||||||
|
__pycache__
|
||||||
|
|
||||||
# Object files
|
# Object files
|
||||||
*.o
|
*.o
|
||||||
*.os
|
*.os
|
||||||
|
|
@ -41,6 +44,10 @@ luac.out
|
||||||
|
|
||||||
.direnv/
|
.direnv/
|
||||||
.testenv/
|
.testenv/
|
||||||
|
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
|
||||||
|
|
|
||||||
9
.luarc.json
Normal file
9
.luarc.json
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"runtime": {
|
||||||
|
"version": "LuaJIT",
|
||||||
|
"pathStrict": true
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"checkTableShape": true
|
||||||
|
}
|
||||||
|
}
|
||||||
183
CHANGELOG.md
183
CHANGELOG.md
|
|
@ -1,5 +1,188 @@
|
||||||
# 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)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* config option to customize floating window title ([#482](https://github.com/stevearc/oil.nvim/issues/482)) ([5d2dfae](https://github.com/stevearc/oil.nvim/commit/5d2dfae655b9b689bd4017b3bdccd52cbee5b92f))
|
||||||
|
* config option to disable lsp file methods ([#477](https://github.com/stevearc/oil.nvim/issues/477)) ([f60bb7f](https://github.com/stevearc/oil.nvim/commit/f60bb7f793477d99ef1acf39e920bf2ca4e644de))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* actions.preview accepts options ([#497](https://github.com/stevearc/oil.nvim/issues/497)) ([cca1631](https://github.com/stevearc/oil.nvim/commit/cca1631d5ea450c09ba72f3951a9e28105a3632c))
|
||||||
|
* add trailing slash to directories on yank_entry ([#504](https://github.com/stevearc/oil.nvim/issues/504)) ([42333bb](https://github.com/stevearc/oil.nvim/commit/42333bb46e34dd47e13927010b1dcd30e6e4ca96))
|
||||||
|
* don't deep merge keymaps ([#510](https://github.com/stevearc/oil.nvim/issues/510)) ([709403c](https://github.com/stevearc/oil.nvim/commit/709403ccd6f22d859c2e42c780ab558ae89284d9))
|
||||||
|
* guard against nil keymaps ([621f8ba](https://github.com/stevearc/oil.nvim/commit/621f8ba4fa821724e9b646732a26fb2e795fe008))
|
||||||
|
* only map ~ for normal mode ([#484](https://github.com/stevearc/oil.nvim/issues/484)) ([ccab9d5](https://github.com/stevearc/oil.nvim/commit/ccab9d5e09e2d0042fbbe5b6bd05e82426247067))
|
||||||
|
* sort keymap help entries by description ([#506](https://github.com/stevearc/oil.nvim/issues/506)) ([52cc8a1](https://github.com/stevearc/oil.nvim/commit/52cc8a1fb35ea6ce1df536143add7ce7215c63c0)), closes [#376](https://github.com/stevearc/oil.nvim/issues/376)
|
||||||
|
|
||||||
|
## [2.12.2](https://github.com/stevearc/oil.nvim/compare/v2.12.1...v2.12.2) (2024-09-10)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* ensure win_options are being set on correct window ([#469](https://github.com/stevearc/oil.nvim/issues/469)) ([30e0438](https://github.com/stevearc/oil.nvim/commit/30e0438ff08f197d7ce4a417445ab97ee72efe2d))
|
||||||
|
* wrap git rm callback in schedule_wrap ([#475](https://github.com/stevearc/oil.nvim/issues/475)) ([b053744](https://github.com/stevearc/oil.nvim/commit/b05374428e5136d9b6c8e1e8e62a75f82283b1f8))
|
||||||
|
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* **view:** avoid running `is_hidden_file` when `show_hidden` is set ([#471](https://github.com/stevearc/oil.nvim/issues/471)) ([0fcd126](https://github.com/stevearc/oil.nvim/commit/0fcd1263a2e8b6200e2b9fd4ab83d40ed8899c54))
|
||||||
|
|
||||||
|
## [2.12.1](https://github.com/stevearc/oil.nvim/compare/v2.12.0...v2.12.1) (2024-08-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* gracefully handle trashing file that does not exist ([70337eb](https://github.com/stevearc/oil.nvim/commit/70337eb77f53cbff0b7f54f403d5b2b0a9430935))
|
||||||
|
* process deletes in dir before moving dir ([349bca8](https://github.com/stevearc/oil.nvim/commit/349bca8c3eae4ab78629ed63ee55cc3458a367c0))
|
||||||
|
|
||||||
|
## [2.12.0](https://github.com/stevearc/oil.nvim/compare/v2.11.0...v2.12.0) (2024-08-17)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add support for `mini.icons` ([#439](https://github.com/stevearc/oil.nvim/issues/439)) ([a543ea5](https://github.com/stevearc/oil.nvim/commit/a543ea598eaef3363fe253e0e11837c1404eb04d))
|
||||||
|
* allow bufnr optional parameter for get_current_dir function ([#440](https://github.com/stevearc/oil.nvim/issues/440)) ([cc23325](https://github.com/stevearc/oil.nvim/commit/cc2332599f8944076fba29ff7960729b3fcdd71b))
|
||||||
|
* disable cursor in preview window ([#433](https://github.com/stevearc/oil.nvim/issues/433)) ([b15e4c1](https://github.com/stevearc/oil.nvim/commit/b15e4c1e647b9ddbb75a31caeb720b3b3ce4db54))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add compatibility for Lua 5.1 ([#456](https://github.com/stevearc/oil.nvim/issues/456)) ([b39a789](https://github.com/stevearc/oil.nvim/commit/b39a78959f3f69e9c1bf43c2634bbddf0af51c3e))
|
||||||
|
* correctly check if `mini.icons` is actually setup ([#441](https://github.com/stevearc/oil.nvim/issues/441)) ([d5e5657](https://github.com/stevearc/oil.nvim/commit/d5e56574f896120b78cdf56dc1132e76057f8877))
|
||||||
|
* cursor sometimes disappears after making changes ([#438](https://github.com/stevearc/oil.nvim/issues/438)) ([b5a1abf](https://github.com/stevearc/oil.nvim/commit/b5a1abfde00eead6814cae3321e4c90ff98cfff1))
|
||||||
|
* Force standard C locale when getting `ls` input for parsing in SSH ([#455](https://github.com/stevearc/oil.nvim/issues/455)) ([71c972f](https://github.com/stevearc/oil.nvim/commit/71c972fbd218723a3c15afcb70421f67340f5a6d))
|
||||||
|
* handle rare case where file watcher outlives buffer ([fcca212](https://github.com/stevearc/oil.nvim/commit/fcca212c2e966fc3dec1d4baf888e670631d25d1))
|
||||||
|
* Handle users and groups with spaces over SSH ([#448](https://github.com/stevearc/oil.nvim/issues/448)) ([a6cea1a](https://github.com/stevearc/oil.nvim/commit/a6cea1a5b9bc9351769fe09a547c62fe4b669abd))
|
||||||
|
* set floating window win_options when buffer changes ([#432](https://github.com/stevearc/oil.nvim/issues/432)) ([b0a6cf9](https://github.com/stevearc/oil.nvim/commit/b0a6cf98982cdcf82b19b0029b734bbbcd24bcc4))
|
||||||
|
|
||||||
|
## [2.11.0](https://github.com/stevearc/oil.nvim/compare/v2.10.0...v2.11.0) (2024-07-01)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* case insensitive sorting ([#429](https://github.com/stevearc/oil.nvim/issues/429)) ([2077cc3](https://github.com/stevearc/oil.nvim/commit/2077cc3358f327aca16c376cdde6ea0b07f14449))
|
||||||
|
* rename experimental_watch_for_changes -> watch_for_changes ([c7c7ce5](https://github.com/stevearc/oil.nvim/commit/c7c7ce5bd47030ee9c60a859f25695647610b8bd))
|
||||||
|
* support preview from floating window ([#403](https://github.com/stevearc/oil.nvim/issues/403)) ([59b3dab](https://github.com/stevearc/oil.nvim/commit/59b3dab6f79e147a0d694ee72c26ae883d323340))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* bug in buffer rendering race condition handling ([f6df58a](https://github.com/stevearc/oil.nvim/commit/f6df58ad370f45dbc18c42ffbaefbcf27df14036))
|
||||||
|
* correctly check group permissions in unix ([#428](https://github.com/stevearc/oil.nvim/issues/428)) ([65c53db](https://github.com/stevearc/oil.nvim/commit/65c53dbe4f2140236590a7568a5f22a77d16be39))
|
||||||
|
* increase loading display delay to avoid flicker ([#424](https://github.com/stevearc/oil.nvim/issues/424)) ([4c574cf](https://github.com/stevearc/oil.nvim/commit/4c574cf4a2de736d2662d52ce086d8bdf87c49df))
|
||||||
|
|
||||||
|
## [2.10.0](https://github.com/stevearc/oil.nvim/compare/v2.9.0...v2.10.0) (2024-06-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add copy filename action ([#391](https://github.com/stevearc/oil.nvim/issues/391)) ([bbc0e67](https://github.com/stevearc/oil.nvim/commit/bbc0e67eebc15342e73b146a50d9b52e6148161b))
|
||||||
|
* keymap actions can be parameterized ([96368e1](https://github.com/stevearc/oil.nvim/commit/96368e13e9b1aaacc570e4825b8787307f0d05e1))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* change unknown action name from error to notification ([e5eb20e](https://github.com/stevearc/oil.nvim/commit/e5eb20e88fc03bf89f371032de77f176158b41d3))
|
||||||
|
* error opening command window from oil float ([#378](https://github.com/stevearc/oil.nvim/issues/378)) ([06a19f7](https://github.com/stevearc/oil.nvim/commit/06a19f77f1a1da37b675635e6f9c5b5d50bcaacd))
|
||||||
|
* hack around glob issues in LSP rename operations ([#386](https://github.com/stevearc/oil.nvim/issues/386)) ([e5312c3](https://github.com/stevearc/oil.nvim/commit/e5312c3a801e7274fa14e6a56aa10a618fed80c3))
|
||||||
|
* incorrect default config actions ([#414](https://github.com/stevearc/oil.nvim/issues/414)) ([c82b26e](https://github.com/stevearc/oil.nvim/commit/c82b26eb4ba35c0eb7ec38d88dd400597fb34883))
|
||||||
|
* notify when changing the current directory ([#406](https://github.com/stevearc/oil.nvim/issues/406)) ([18272ab](https://github.com/stevearc/oil.nvim/commit/18272aba9d00a3176a5443d50dbb4464acc167bd))
|
||||||
|
* throw error on vim.has call within the lsp/workspace.lua ([#411](https://github.com/stevearc/oil.nvim/issues/411)) ([61f1967](https://github.com/stevearc/oil.nvim/commit/61f1967222365474c6cf7953c569cc94dbcc7acd))
|
||||||
|
* vim.notify call error ([76bfc25](https://github.com/stevearc/oil.nvim/commit/76bfc25520e4edc98d089d023b4ed06013639849))
|
||||||
|
|
||||||
|
## [2.9.0](https://github.com/stevearc/oil.nvim/compare/v2.8.0...v2.9.0) (2024-05-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* can restore Oil progress window when minimized ([fa3820e](https://github.com/stevearc/oil.nvim/commit/fa3820ebf1e8ccf5c7c0f3626d499b2c1aa8bc50))
|
||||||
|
* experimental support for git operations ([#290](https://github.com/stevearc/oil.nvim/issues/290)) ([1f05774](https://github.com/stevearc/oil.nvim/commit/1f05774e1c2dbc1940104b5c950d5c7b65ec6e0b))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* duplicate create actions ([#334](https://github.com/stevearc/oil.nvim/issues/334)) ([354c530](https://github.com/stevearc/oil.nvim/commit/354c53080a6d7f4f0b2f0cc12e53bede2480b9e5))
|
||||||
|
* error when opening files from floating oil window ([#355](https://github.com/stevearc/oil.nvim/issues/355)) ([2bc56ad](https://github.com/stevearc/oil.nvim/commit/2bc56ad68afd092af1b2e77dd5d61e156938564c))
|
||||||
|
* git mv errors when moving empty directory ([#358](https://github.com/stevearc/oil.nvim/issues/358)) ([6a7a10b](https://github.com/stevearc/oil.nvim/commit/6a7a10b6117aface6a25b54906140ad4f7fdabfc))
|
||||||
|
* gracefully handle new dirs with trailing backslash on windows ([#336](https://github.com/stevearc/oil.nvim/issues/336)) ([be0a1ec](https://github.com/stevearc/oil.nvim/commit/be0a1ecbf0541692a1b9b6e8ea15f5f57db8747a))
|
||||||
|
* icon column highlight parameter ([#366](https://github.com/stevearc/oil.nvim/issues/366)) ([752563c](https://github.com/stevearc/oil.nvim/commit/752563c59d64a5764cc0743d4fa0aac9ae4a2640))
|
||||||
|
* race condition when entering oil buffer ([#321](https://github.com/stevearc/oil.nvim/issues/321)) ([c86e484](https://github.com/stevearc/oil.nvim/commit/c86e48407b8a45f9aa8acb2b4512b384ea1eec84))
|
||||||
|
* **ssh:** bad argument when editing files over ssh ([#370](https://github.com/stevearc/oil.nvim/issues/370)) ([aa0c00c](https://github.com/stevearc/oil.nvim/commit/aa0c00c7fd51982ac476d165cd021f348cf5ea71))
|
||||||
|
* **ssh:** config option to pass extra args to SCP ([#340](https://github.com/stevearc/oil.nvim/issues/340)) ([3abb607](https://github.com/stevearc/oil.nvim/commit/3abb6077d7d6b09f5eb794b8764223b3027f6807))
|
||||||
|
* **ssh:** garbled output when directory has broken symlinks ([bcfc0a2](https://github.com/stevearc/oil.nvim/commit/bcfc0a2e01def5019aa14fac2fc6de20dedb6d3d))
|
||||||
|
* support visual mode when preview window is open ([#315](https://github.com/stevearc/oil.nvim/issues/315)) ([f41d7e7](https://github.com/stevearc/oil.nvim/commit/f41d7e7cd8e4028b03c35d847b4396790ac8bb2d))
|
||||||
|
* **windows:** convert posix paths before matching LSP watch globs ([#374](https://github.com/stevearc/oil.nvim/issues/374)) ([f630887](https://github.com/stevearc/oil.nvim/commit/f630887cd845a7341bc16488fe8aaecffe3aaa8a))
|
||||||
|
* **windows:** file operation preview uses only backslash path separator ([#336](https://github.com/stevearc/oil.nvim/issues/336)) ([96f0983](https://github.com/stevearc/oil.nvim/commit/96f0983e754694e592d4313f583cd31eaebfa80d))
|
||||||
|
* **windows:** navigating into drive letter root directories ([#341](https://github.com/stevearc/oil.nvim/issues/341)) ([f3a31eb](https://github.com/stevearc/oil.nvim/commit/f3a31eba24587bc038592103d8f7e64648292115))
|
||||||
|
* **windows:** treat both backslash and frontslash as path separators ([#336](https://github.com/stevearc/oil.nvim/issues/336)) ([3b3a6b2](https://github.com/stevearc/oil.nvim/commit/3b3a6b23a120e69ddc980c9d32840ecd521fbff9))
|
||||||
|
|
||||||
## [2.8.0](https://github.com/stevearc/oil.nvim/compare/v2.7.0...v2.8.0) (2024-04-19)
|
## [2.8.0](https://github.com/stevearc/oil.nvim/compare/v2.7.0...v2.8.0) (2024-04-19)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
53
Makefile
53
Makefile
|
|
@ -1,27 +1,66 @@
|
||||||
.PHONY: all doc test lint fastlint clean
|
## help: print this help message
|
||||||
|
.PHONY: help
|
||||||
|
help:
|
||||||
|
@echo 'Usage:'
|
||||||
|
@sed -n 's/^##//p' ${MAKEFILE_LIST} | column -t -s ':' | sed -e 's/^/ /'
|
||||||
|
|
||||||
|
## all: generate docs, lint, and run tests
|
||||||
|
.PHONY: all
|
||||||
all: doc lint test
|
all: doc lint test
|
||||||
|
|
||||||
doc: scripts/nvim_doc_tools
|
venv:
|
||||||
python scripts/main.py generate
|
python3 -m venv venv
|
||||||
python scripts/main.py lint
|
venv/bin/pip install -r scripts/requirements.txt
|
||||||
|
|
||||||
|
## doc: generate documentation
|
||||||
|
.PHONY: doc
|
||||||
|
doc: scripts/nvim_doc_tools venv
|
||||||
|
venv/bin/python scripts/main.py generate
|
||||||
|
venv/bin/python scripts/main.py lint
|
||||||
|
|
||||||
|
## test: run tests
|
||||||
|
.PHONY: test
|
||||||
test:
|
test:
|
||||||
./run_tests.sh
|
./run_tests.sh
|
||||||
|
|
||||||
|
## lint: run linters and LuaLS typechecking
|
||||||
|
.PHONY: lint
|
||||||
lint: scripts/nvim-typecheck-action fastlint
|
lint: scripts/nvim-typecheck-action fastlint
|
||||||
./scripts/nvim-typecheck-action/typecheck.sh --workdir scripts/nvim-typecheck-action lua
|
./scripts/nvim-typecheck-action/typecheck.sh --workdir scripts/nvim-typecheck-action lua
|
||||||
|
|
||||||
fastlint: scripts/nvim_doc_tools
|
## fastlint: run only fast linters
|
||||||
python scripts/main.py lint
|
.PHONY: fastlint
|
||||||
|
fastlint: scripts/nvim_doc_tools venv
|
||||||
|
venv/bin/python scripts/main.py lint
|
||||||
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
|
||||||
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
rm -rf scripts/nvim_doc_tools scripts/nvim-typecheck-action
|
rm -rf scripts/nvim_doc_tools scripts/nvim-typecheck-action venv .testenv perf/tmp profile.json
|
||||||
|
|
|
||||||
154
README.md
154
README.md
|
|
@ -11,6 +11,8 @@ https://user-images.githubusercontent.com/506791/209727111-6b4a11f4-634a-4efa-94
|
||||||
- [Quick start](#quick-start)
|
- [Quick start](#quick-start)
|
||||||
- [Options](#options)
|
- [Options](#options)
|
||||||
- [Adapters](#adapters)
|
- [Adapters](#adapters)
|
||||||
|
- [Recipes](#recipes)
|
||||||
|
- [Third-party extensions](#third-party-extensions)
|
||||||
- [API](#api)
|
- [API](#api)
|
||||||
- [FAQ](#faq)
|
- [FAQ](#faq)
|
||||||
|
|
||||||
|
|
@ -19,7 +21,9 @@ https://user-images.githubusercontent.com/506791/209727111-6b4a11f4-634a-4efa-94
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- Neovim 0.8+
|
- Neovim 0.8+
|
||||||
- (optional) [nvim-web-devicons](https://github.com/nvim-tree/nvim-web-devicons) for file icons
|
- Icon provider plugin (optional)
|
||||||
|
- [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
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
|
@ -31,9 +35,14 @@ oil.nvim supports all the usual plugin managers
|
||||||
```lua
|
```lua
|
||||||
{
|
{
|
||||||
'stevearc/oil.nvim',
|
'stevearc/oil.nvim',
|
||||||
|
---@module 'oil'
|
||||||
|
---@type oil.SetupOpts
|
||||||
opts = {},
|
opts = {},
|
||||||
-- Optional dependencies
|
-- Optional dependencies
|
||||||
dependencies = { "nvim-tree/nvim-web-devicons" },
|
dependencies = { { "nvim-mini/mini.icons", opts = {} } },
|
||||||
|
-- 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,
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -126,7 +135,7 @@ You can open a directory with `:edit <path>` or `:Oil <path>`. To open oil in a
|
||||||
```lua
|
```lua
|
||||||
require("oil").setup({
|
require("oil").setup({
|
||||||
-- 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 still want to use netrw.
|
-- Set to false if you want some other plugin (e.g. netrw) to open when you edit directories.
|
||||||
default_file_explorer = true,
|
default_file_explorer = true,
|
||||||
-- Id is automatically added at the beginning, and name at the end
|
-- Id is automatically added at the beginning, and name at the end
|
||||||
-- See :help oil-columns
|
-- See :help oil-columns
|
||||||
|
|
@ -164,6 +173,8 @@ require("oil").setup({
|
||||||
-- Note that the cleanup process only starts when none of the oil buffers are currently displayed
|
-- Note that the cleanup process only starts when none of the oil buffers are currently displayed
|
||||||
cleanup_delay_ms = 2000,
|
cleanup_delay_ms = 2000,
|
||||||
lsp_file_methods = {
|
lsp_file_methods = {
|
||||||
|
-- Enable or disable LSP file operations
|
||||||
|
enabled = true,
|
||||||
-- Time to wait for LSP file operations to complete before skipping
|
-- Time to wait for LSP file operations to complete before skipping
|
||||||
timeout_ms = 1000,
|
timeout_ms = 1000,
|
||||||
-- Set to true to autosave buffers that are updated with LSP willRenameFiles
|
-- Set to true to autosave buffers that are updated with LSP willRenameFiles
|
||||||
|
|
@ -174,7 +185,7 @@ require("oil").setup({
|
||||||
-- Set to `false` to disable, or "name" to keep it on the file names
|
-- Set to `false` to disable, or "name" to keep it on the file names
|
||||||
constrain_cursor = "editable",
|
constrain_cursor = "editable",
|
||||||
-- Set to true to watch the filesystem for changes and reload oil
|
-- Set to true to watch the filesystem for changes and reload oil
|
||||||
experimental_watch_for_changes = false,
|
watch_for_changes = false,
|
||||||
-- Keymaps in oil buffer. Can be any value that `vim.keymap.set` accepts OR a table of keymap
|
-- Keymaps in oil buffer. Can be any value that `vim.keymap.set` accepts OR a table of keymap
|
||||||
-- options with a `callback` (e.g. { callback = function() ... end, desc = "", mode = "n" })
|
-- options with a `callback` (e.g. { callback = function() ... end, desc = "", mode = "n" })
|
||||||
-- Additionally, if it is a string that matches "actions.<name>",
|
-- Additionally, if it is a string that matches "actions.<name>",
|
||||||
|
|
@ -182,26 +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_vsplit",
|
["<C-s>"] = { "actions.select", opts = { vertical = true } },
|
||||||
["<C-h>"] = "actions.select_split",
|
["<C-h>"] = { "actions.select", opts = { horizontal = true } },
|
||||||
["<C-t>"] = "actions.select_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.tcd",
|
["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" },
|
||||||
},
|
|
||||||
-- Configuration for the floating keymaps help window
|
|
||||||
keymaps_help = {
|
|
||||||
border = "rounded",
|
|
||||||
},
|
},
|
||||||
-- 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,
|
||||||
|
|
@ -210,40 +217,82 @@ 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
|
||||||
|
case_insensitive = false,
|
||||||
sort = {
|
sort = {
|
||||||
-- sort order can be "asc" or "desc"
|
-- sort order can be "asc" or "desc"
|
||||||
-- see :help oil-columns to see which columns are sortable
|
-- see :help oil-columns to see which columns are sortable
|
||||||
{ "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_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
|
||||||
|
git = {
|
||||||
|
-- Return true to automatically git add/mv/rm files
|
||||||
|
add = function(path)
|
||||||
|
return false
|
||||||
|
end,
|
||||||
|
mv = function(src_path, dest_path)
|
||||||
|
return false
|
||||||
|
end,
|
||||||
|
rm = function(path)
|
||||||
|
return false
|
||||||
|
end,
|
||||||
},
|
},
|
||||||
-- Configuration for the floating window in oil.open_float
|
-- Configuration for the floating window in oil.open_float
|
||||||
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,
|
||||||
},
|
},
|
||||||
|
-- optionally override the oil buffers window title with custom function: fun(winid: integer): string
|
||||||
|
get_win_title = nil,
|
||||||
|
-- preview_split: Split direction: "auto", "left", "right", "above", "below".
|
||||||
|
preview_split = "auto",
|
||||||
-- This is the config that will be passed to nvim_open_win.
|
-- This is the config that will be passed to nvim_open_win.
|
||||||
-- Change values here to customize the layout
|
-- Change values here to customize the layout
|
||||||
override = function(conf)
|
override = function(conf)
|
||||||
return conf
|
return conf
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
-- Configuration for the actions floating preview window
|
-- Configuration for the file preview window
|
||||||
preview = {
|
preview_win = {
|
||||||
|
-- Whether the preview window is automatically updated when the cursor is moved
|
||||||
|
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
|
||||||
|
confirmation = {
|
||||||
-- Width dimensions can be integers or a float between 0 and 1 (e.g. 0.4 for 40%)
|
-- Width dimensions can be integers or a float between 0 and 1 (e.g. 0.4 for 40%)
|
||||||
-- min_width and max_width can be a single value or a list of mixed integer/float types.
|
-- min_width and max_width can be a single value or a list of mixed integer/float types.
|
||||||
-- max_width = {100, 0.8} means "the lesser of 100 columns or 80% of total"
|
-- max_width = {100, 0.8} means "the lesser of 100 columns or 80% of total"
|
||||||
|
|
@ -260,12 +309,10 @@ 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,
|
||||||
},
|
},
|
||||||
-- Whether the preview window is automatically updated when the cursor is moved
|
|
||||||
update_on_cursor_moved = true,
|
|
||||||
},
|
},
|
||||||
-- Configuration for the floating progress window
|
-- Configuration for the floating progress window
|
||||||
progress = {
|
progress = {
|
||||||
|
|
@ -275,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,
|
||||||
|
|
@ -283,7 +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
|
||||||
|
keymaps_help = {
|
||||||
|
border = nil,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
@ -306,6 +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
|
||||||
|
|
||||||
|
- [Toggle file detail view](doc/recipes.md#toggle-file-detail-view)
|
||||||
|
- [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)
|
||||||
|
|
||||||
|
## 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 -->
|
||||||
|
|
@ -317,11 +392,12 @@ Note that at the moment the ssh adapter does not support Windows machines, and i
|
||||||
- [set_sort(sort)](doc/api.md#set_sortsort)
|
- [set_sort(sort)](doc/api.md#set_sortsort)
|
||||||
- [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()](doc/api.md#get_current_dir)
|
- [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)
|
||||||
- [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)
|
||||||
- [setup(opts)](doc/api.md#setupopts)
|
- [setup(opts)](doc/api.md#setupopts)
|
||||||
|
|
@ -345,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
|
||||||
|
|
||||||
|
|
@ -361,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).
|
||||||
|
|
|
||||||
140
doc/api.md
140
doc/api.md
|
|
@ -9,11 +9,12 @@
|
||||||
- [set_sort(sort)](#set_sortsort)
|
- [set_sort(sort)](#set_sortsort)
|
||||||
- [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()](#get_current_dir)
|
- [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)
|
||||||
- [select(opts, callback)](#selectopts-callback)
|
- [select(opts, callback)](#selectopts-callback)
|
||||||
- [save(opts, cb)](#saveopts-cb)
|
- [save(opts, cb)](#saveopts-cb)
|
||||||
- [setup(opts)](#setupopts)
|
- [setup(opts)](#setupopts)
|
||||||
|
|
@ -58,18 +59,23 @@ Change the display columns for oil
|
||||||
`set_sort(sort)` \
|
`set_sort(sort)` \
|
||||||
Change the sort order for oil
|
Change the sort order for oil
|
||||||
|
|
||||||
| Param | Type | Desc |
|
| Param | Type | Desc |
|
||||||
| ----- | ---------- | ---- |
|
| ----- | ---------------- | ------------------------------------------------------------------------------------- |
|
||||||
| sort | `string[]` | [] |
|
| sort | `oil.SortSpec[]` | List of columns plus direction. See :help oil-columns to see which ones are sortable. |
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
```lua
|
||||||
|
require("oil").set_sort({ { "type", "asc" }, { "size", "desc" } })
|
||||||
|
```
|
||||||
|
|
||||||
## set_is_hidden_file(is_hidden_file)
|
## set_is_hidden_file(is_hidden_file)
|
||||||
|
|
||||||
`set_is_hidden_file(is_hidden_file)` \
|
`set_is_hidden_file(is_hidden_file)` \
|
||||||
Change how oil determines if the file is hidden
|
Change how oil determines if the file is hidden
|
||||||
|
|
||||||
| Param | Type | Desc |
|
| Param | Type | Desc |
|
||||||
| -------------- | ----------------------------------------------------- | -------------------------------------------- |
|
| -------------- | ------------------------------------------------ | -------------------------------------------- |
|
||||||
| is_hidden_file | `fun(filename: string, bufnr: nil\|integer): boolean` | Return true if the file/dir should be hidden |
|
| is_hidden_file | `fun(filename: string, bufnr: integer): boolean` | Return true if the file/dir should be hidden |
|
||||||
|
|
||||||
## toggle_hidden()
|
## toggle_hidden()
|
||||||
|
|
||||||
|
|
@ -77,71 +83,109 @@ Change how oil determines if the file is hidden
|
||||||
Toggle hidden files and directories
|
Toggle hidden files and directories
|
||||||
|
|
||||||
|
|
||||||
## get_current_dir()
|
## get_current_dir(bufnr)
|
||||||
|
|
||||||
`get_current_dir(): nil|string` \
|
`get_current_dir(bufnr): nil|string` \
|
||||||
Get the current directory
|
Get the current directory
|
||||||
|
|
||||||
|
| Param | Type | Desc |
|
||||||
|
| ----- | -------------- | ---- |
|
||||||
|
| 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)` \
|
||||||
|
Preview the entry under the cursor in a split
|
||||||
|
|
||||||
|
| Param | Type | Desc |
|
||||||
|
| ----------- | ------------------------------------------------------- | ---------------------------------------------- |
|
||||||
|
| opts | `nil\|oil.OpenPreviewOpts` | |
|
||||||
|
| >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 |
|
||||||
|
| callback | `nil\|fun(err: nil\|string)` | Called once the preview window has been opened |
|
||||||
|
|
||||||
## select(opts, callback)
|
## select(opts, callback)
|
||||||
|
|
||||||
`select(opts, callback)` \
|
`select(opts, callback)` \
|
||||||
Select the entry under the cursor
|
Select the entry under the cursor
|
||||||
|
|
||||||
| Param | Type | Desc | |
|
| Param | Type | Desc |
|
||||||
| -------- | ---------------------------- | -------------------------------------------------- | ---------------------------------------------------- |
|
| ----------------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| opts | `nil\|table` | | |
|
| opts | `nil\|oil.SelectOpts` | |
|
||||||
| | vertical | `boolean` | Open the buffer in a vertical split |
|
| >vertical | `nil\|boolean` | Open the buffer in a vertical split |
|
||||||
| | horizontal | `boolean` | Open the buffer in a horizontal split |
|
| >horizontal | `nil\|boolean` | Open the buffer in a horizontal split |
|
||||||
| | split | `"aboveleft"\|"belowright"\|"topleft"\|"botright"` | Split modifier |
|
| >split | `nil\|"aboveleft"\|"belowright"\|"topleft"\|"botright"` | Split modifier |
|
||||||
| | preview | `boolean` | Open the buffer in a preview window |
|
| >tab | `nil\|boolean` | Open the buffer in a new tab |
|
||||||
| | tab | `boolean` | Open the buffer in a new tab |
|
| >close | `nil\|boolean` | Close the original oil buffer once selection is made |
|
||||||
| | close | `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)
|
||||||
|
|
||||||
`save(opts, cb)` \
|
`save(opts, cb)` \
|
||||||
Save all changes
|
Save all changes
|
||||||
|
|
||||||
| Param | Type | Desc | |
|
| Param | Type | Desc |
|
||||||
| ----- | ---------------------------- | ------------------------------- | ------------------------------------------------------------------------------------------- |
|
| -------- | ---------------------------- | ------------------------------------------------------------------------------------------- |
|
||||||
| opts | `nil\|table` | | |
|
| opts | `nil\|table` | |
|
||||||
| | confirm | `nil\|boolean` | Show confirmation when true, never when false, respect skip_confirm_for_simple_edits if nil |
|
| >confirm | `nil\|boolean` | Show confirmation when true, never when false, respect skip_confirm_for_simple_edits if nil |
|
||||||
| cb | `nil\|fun(err: nil\|string)` | Called when mutations complete. | |
|
| cb | `nil\|fun(err: nil\|string)` | Called when mutations complete. |
|
||||||
|
|
||||||
**Note:**
|
**Note:**
|
||||||
<pre>
|
<pre>
|
||||||
|
|
@ -153,9 +197,9 @@ If you provide your own callback function, there will be no notification for err
|
||||||
`setup(opts)` \
|
`setup(opts)` \
|
||||||
Initialize oil
|
Initialize oil
|
||||||
|
|
||||||
| Param | Type | Desc |
|
| Param | Type | Desc |
|
||||||
| ----- | ------------ | ---- |
|
| ----- | -------------------- | ---- |
|
||||||
| opts | `nil\|table` | |
|
| opts | `oil.setupOpts\|nil` | |
|
||||||
|
|
||||||
|
|
||||||
<!-- /API -->
|
<!-- /API -->
|
||||||
|
|
|
||||||
383
doc/oil.txt
383
doc/oil.txt
|
|
@ -17,7 +17,7 @@ CONFIG *oil-confi
|
||||||
>lua
|
>lua
|
||||||
require("oil").setup({
|
require("oil").setup({
|
||||||
-- 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 still want to use netrw.
|
-- Set to false if you want some other plugin (e.g. netrw) to open when you edit directories.
|
||||||
default_file_explorer = true,
|
default_file_explorer = true,
|
||||||
-- Id is automatically added at the beginning, and name at the end
|
-- Id is automatically added at the beginning, and name at the end
|
||||||
-- See :help oil-columns
|
-- See :help oil-columns
|
||||||
|
|
@ -55,6 +55,8 @@ CONFIG *oil-confi
|
||||||
-- Note that the cleanup process only starts when none of the oil buffers are currently displayed
|
-- Note that the cleanup process only starts when none of the oil buffers are currently displayed
|
||||||
cleanup_delay_ms = 2000,
|
cleanup_delay_ms = 2000,
|
||||||
lsp_file_methods = {
|
lsp_file_methods = {
|
||||||
|
-- Enable or disable LSP file operations
|
||||||
|
enabled = true,
|
||||||
-- Time to wait for LSP file operations to complete before skipping
|
-- Time to wait for LSP file operations to complete before skipping
|
||||||
timeout_ms = 1000,
|
timeout_ms = 1000,
|
||||||
-- Set to true to autosave buffers that are updated with LSP willRenameFiles
|
-- Set to true to autosave buffers that are updated with LSP willRenameFiles
|
||||||
|
|
@ -65,7 +67,7 @@ CONFIG *oil-confi
|
||||||
-- Set to `false` to disable, or "name" to keep it on the file names
|
-- Set to `false` to disable, or "name" to keep it on the file names
|
||||||
constrain_cursor = "editable",
|
constrain_cursor = "editable",
|
||||||
-- Set to true to watch the filesystem for changes and reload oil
|
-- Set to true to watch the filesystem for changes and reload oil
|
||||||
experimental_watch_for_changes = false,
|
watch_for_changes = false,
|
||||||
-- Keymaps in oil buffer. Can be any value that `vim.keymap.set` accepts OR a table of keymap
|
-- Keymaps in oil buffer. Can be any value that `vim.keymap.set` accepts OR a table of keymap
|
||||||
-- options with a `callback` (e.g. { callback = function() ... end, desc = "", mode = "n" })
|
-- options with a `callback` (e.g. { callback = function() ... end, desc = "", mode = "n" })
|
||||||
-- Additionally, if it is a string that matches "actions.<name>",
|
-- Additionally, if it is a string that matches "actions.<name>",
|
||||||
|
|
@ -73,26 +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_vsplit",
|
["<C-s>"] = { "actions.select", opts = { vertical = true } },
|
||||||
["<C-h>"] = "actions.select_split",
|
["<C-h>"] = { "actions.select", opts = { horizontal = true } },
|
||||||
["<C-t>"] = "actions.select_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.tcd",
|
["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" },
|
||||||
},
|
|
||||||
-- Configuration for the floating keymaps help window
|
|
||||||
keymaps_help = {
|
|
||||||
border = "rounded",
|
|
||||||
},
|
},
|
||||||
-- 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,
|
||||||
|
|
@ -101,40 +99,82 @@ 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
|
||||||
|
case_insensitive = false,
|
||||||
sort = {
|
sort = {
|
||||||
-- sort order can be "asc" or "desc"
|
-- sort order can be "asc" or "desc"
|
||||||
-- see :help oil-columns to see which columns are sortable
|
-- see :help oil-columns to see which columns are sortable
|
||||||
{ "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_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
|
||||||
|
git = {
|
||||||
|
-- Return true to automatically git add/mv/rm files
|
||||||
|
add = function(path)
|
||||||
|
return false
|
||||||
|
end,
|
||||||
|
mv = function(src_path, dest_path)
|
||||||
|
return false
|
||||||
|
end,
|
||||||
|
rm = function(path)
|
||||||
|
return false
|
||||||
|
end,
|
||||||
},
|
},
|
||||||
-- Configuration for the floating window in oil.open_float
|
-- Configuration for the floating window in oil.open_float
|
||||||
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,
|
||||||
},
|
},
|
||||||
|
-- optionally override the oil buffers window title with custom function: fun(winid: integer): string
|
||||||
|
get_win_title = nil,
|
||||||
|
-- preview_split: Split direction: "auto", "left", "right", "above", "below".
|
||||||
|
preview_split = "auto",
|
||||||
-- This is the config that will be passed to nvim_open_win.
|
-- This is the config that will be passed to nvim_open_win.
|
||||||
-- Change values here to customize the layout
|
-- Change values here to customize the layout
|
||||||
override = function(conf)
|
override = function(conf)
|
||||||
return conf
|
return conf
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
-- Configuration for the actions floating preview window
|
-- Configuration for the file preview window
|
||||||
preview = {
|
preview_win = {
|
||||||
|
-- Whether the preview window is automatically updated when the cursor is moved
|
||||||
|
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
|
||||||
|
confirmation = {
|
||||||
-- Width dimensions can be integers or a float between 0 and 1 (e.g. 0.4 for 40%)
|
-- Width dimensions can be integers or a float between 0 and 1 (e.g. 0.4 for 40%)
|
||||||
-- min_width and max_width can be a single value or a list of mixed integer/float types.
|
-- min_width and max_width can be a single value or a list of mixed integer/float types.
|
||||||
-- max_width = {100, 0.8} means "the lesser of 100 columns or 80% of total"
|
-- max_width = {100, 0.8} means "the lesser of 100 columns or 80% of total"
|
||||||
|
|
@ -151,12 +191,10 @@ 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,
|
||||||
},
|
},
|
||||||
-- Whether the preview window is automatically updated when the cursor is moved
|
|
||||||
update_on_cursor_moved = true,
|
|
||||||
},
|
},
|
||||||
-- Configuration for the floating progress window
|
-- Configuration for the floating progress window
|
||||||
progress = {
|
progress = {
|
||||||
|
|
@ -166,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,
|
||||||
|
|
@ -174,7 +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
|
||||||
|
keymaps_help = {
|
||||||
|
border = nil,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
<
|
<
|
||||||
|
|
@ -240,61 +282,111 @@ set_sort({sort}) *oil.set_sor
|
||||||
Change the sort order for oil
|
Change the sort order for oil
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
{sort} `string[]` []
|
{sort} `oil.SortSpec[]` List of columns plus direction. See :help oil-
|
||||||
|
columns to see which ones are sortable.
|
||||||
|
|
||||||
|
Examples: >lua
|
||||||
|
require("oil").set_sort({ { "type", "asc" }, { "size", "desc" } })
|
||||||
|
<
|
||||||
|
|
||||||
set_is_hidden_file({is_hidden_file}) *oil.set_is_hidden_file*
|
set_is_hidden_file({is_hidden_file}) *oil.set_is_hidden_file*
|
||||||
Change how oil determines if the file is hidden
|
Change how oil determines if the file is hidden
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
{is_hidden_file} `fun(filename: string, bufnr: nil|integer): boolean` Retu
|
{is_hidden_file} `fun(filename: string, bufnr: integer): boolean` Return
|
||||||
rn true if the file/dir should be hidden
|
true if the file/dir should be hidden
|
||||||
|
|
||||||
toggle_hidden() *oil.toggle_hidden*
|
toggle_hidden() *oil.toggle_hidden*
|
||||||
Toggle hidden files and directories
|
Toggle hidden files and directories
|
||||||
|
|
||||||
|
|
||||||
get_current_dir(): nil|string *oil.get_current_dir*
|
get_current_dir({bufnr}): nil|string *oil.get_current_dir*
|
||||||
Get the current directory
|
Get the current directory
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
{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*
|
||||||
|
Preview the entry under the cursor in a split
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
{opts} `nil|oil.OpenPreviewOpts`
|
||||||
|
{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
|
||||||
|
{callback} `nil|fun(err: nil|string)` Called once the preview window has
|
||||||
|
been opened
|
||||||
|
|
||||||
select({opts}, {callback}) *oil.select*
|
select({opts}, {callback}) *oil.select*
|
||||||
Select the entry under the cursor
|
Select the entry under the cursor
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
{opts} `nil|table`
|
{opts} `nil|oil.SelectOpts`
|
||||||
{vertical} `boolean` Open the buffer in a vertical split
|
{vertical} `nil|boolean` Open the buffer in a vertical split
|
||||||
{horizontal} `boolean` Open the buffer in a horizontal split
|
{horizontal} `nil|boolean` Open the buffer in a horizontal split
|
||||||
{split} `"aboveleft"|"belowright"|"topleft"|"botright"` Split
|
{split} `nil|"aboveleft"|"belowright"|"topleft"|"botright"` Split
|
||||||
modifier
|
modifier
|
||||||
{preview} `boolean` Open the buffer in a preview window
|
{tab} `nil|boolean` Open the buffer in a new tab
|
||||||
{tab} `boolean` Open the buffer in a new tab
|
{close} `nil|boolean` Close the original oil buffer once
|
||||||
{close} `boolean` Close the original oil buffer once selection is
|
selection is made
|
||||||
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
|
||||||
|
|
||||||
|
|
@ -314,7 +406,7 @@ setup({opts}) *oil.setu
|
||||||
Initialize oil
|
Initialize oil
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
{opts} `nil|table`
|
{opts} `oil.setupOpts|nil`
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
COLUMNS *oil-columns*
|
COLUMNS *oil-columns*
|
||||||
|
|
@ -330,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*
|
||||||
|
|
@ -339,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
|
||||||
|
|
@ -346,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
|
||||||
|
|
@ -362,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
|
||||||
|
|
@ -371,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*
|
||||||
|
|
@ -381,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*
|
||||||
|
|
@ -391,50 +489,94 @@ 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)
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
ACTIONS *oil-actions*
|
ACTIONS *oil-actions*
|
||||||
|
|
||||||
These are actions that can be used in the `keymaps` section of config options.
|
The `keymaps` option in `oil.setup` allow you to create mappings using all the same parameters as |vim.keymap.set|.
|
||||||
You can also call them directly with
|
>lua
|
||||||
|
keymaps = {
|
||||||
|
-- Mappings can be a string
|
||||||
|
["~"] = "<cmd>edit $HOME<CR>",
|
||||||
|
-- Mappings can be a function
|
||||||
|
["gd"] = function()
|
||||||
|
require("oil").set_columns({ "icon", "permissions", "size", "mtime" })
|
||||||
|
end,
|
||||||
|
-- You can pass additional opts to vim.keymap.set by using
|
||||||
|
-- a table with the mapping as the first element.
|
||||||
|
["<leader>ff"] = {
|
||||||
|
function()
|
||||||
|
require("telescope.builtin").find_files({
|
||||||
|
cwd = require("oil").get_current_dir()
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
mode = "n",
|
||||||
|
nowait = true,
|
||||||
|
desc = "Find files in the current directory"
|
||||||
|
},
|
||||||
|
-- Mappings that are a string starting with "actions." will be
|
||||||
|
-- one of the built-in actions, documented below.
|
||||||
|
["`"] = "actions.tcd",
|
||||||
|
-- Some actions have parameters. These are passed in via the `opts` key.
|
||||||
|
["<leader>:"] = {
|
||||||
|
"actions.open_cmdline",
|
||||||
|
opts = {
|
||||||
|
shorten_path = true,
|
||||||
|
modify = ":h",
|
||||||
|
},
|
||||||
|
desc = "Open the command line with the current directory as an argument",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Below are the actions that can be used in the `keymaps` section of config
|
||||||
|
options. You can refer to them as strings (e.g. "actions.<action_name>") or you
|
||||||
|
can use the functions directly with
|
||||||
`require("oil.actions").action_name.callback()`
|
`require("oil.actions").action_name.callback()`
|
||||||
|
|
||||||
add_to_loclist *actions.add_to_loclist*
|
|
||||||
Adds files in the current oil directory to the location list, keeping the
|
|
||||||
previous entries.
|
|
||||||
|
|
||||||
add_to_qflist *actions.add_to_qflist*
|
|
||||||
Adds files in the current oil directory to the quickfix list, keeping the
|
|
||||||
previous entries.
|
|
||||||
|
|
||||||
cd *actions.cd*
|
cd *actions.cd*
|
||||||
:cd to the current oil directory
|
:cd to the current oil directory
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
{scope} `nil|"tab"|"win"` Scope of the directory change (e.g. use |:tcd|
|
||||||
|
or |:lcd|)
|
||||||
|
{silent} `boolean` Do not show a message when changing directories
|
||||||
|
|
||||||
change_sort *actions.change_sort*
|
change_sort *actions.change_sort*
|
||||||
Change the sort order
|
Change the sort order
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
{sort} `oil.SortSpec[]` List of columns plus direction (see
|
||||||
|
|oil.set_sort|) instead of interactive selection
|
||||||
|
|
||||||
close *actions.close*
|
close *actions.close*
|
||||||
Close oil and restore original buffer
|
Close oil and restore original buffer
|
||||||
|
|
||||||
copy_entry_path *actions.copy_entry_path*
|
Parameters:
|
||||||
Yank the filepath of the entry under the cursor to a register
|
{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
|
||||||
|
|
||||||
open_cmdline_dir *actions.open_cmdline_dir*
|
Parameters:
|
||||||
Open vim cmdline with current directory as an argument
|
{modify} `string` Modify the path with |fnamemodify()| using this as
|
||||||
|
the mods argument
|
||||||
|
{shorten_path} `boolean` Use relative paths when possible
|
||||||
|
|
||||||
open_cwd *actions.open_cwd*
|
open_cwd *actions.open_cwd*
|
||||||
Open oil in Neovim's current working directory
|
Open oil in Neovim's current working directory
|
||||||
|
|
@ -448,72 +590,135 @@ 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
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
{horizontal} `boolean` Open the buffer in a horizontal split
|
||||||
|
{split} `"aboveleft"|"belowright"|"topleft"|"botright"` Split
|
||||||
|
modifier
|
||||||
|
{vertical} `boolean` Open the buffer in a vertical split
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
refresh *actions.refresh*
|
refresh *actions.refresh*
|
||||||
Refresh current directory list
|
Refresh current directory list
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
{force} `boolean` When true, do not prompt user if they will be discarding
|
||||||
|
changes
|
||||||
|
|
||||||
select *actions.select*
|
select *actions.select*
|
||||||
Open the entry under the cursor
|
Open the entry under the cursor
|
||||||
|
|
||||||
select_split *actions.select_split*
|
Parameters:
|
||||||
Open the entry under the cursor in a horizontal split
|
{close} `boolean` Close the original oil buffer once selection is
|
||||||
|
made
|
||||||
select_tab *actions.select_tab*
|
{horizontal} `boolean` Open the buffer in a horizontal split
|
||||||
Open the entry under the cursor in a new tab
|
{split} `"aboveleft"|"belowright"|"topleft"|"botright"` Split
|
||||||
|
modifier
|
||||||
select_vsplit *actions.select_vsplit*
|
{tab} `boolean` Open the buffer in a new tab
|
||||||
Open the entry under the cursor in a vertical split
|
{vertical} `boolean` Open the buffer in a vertical split
|
||||||
|
|
||||||
send_to_loclist *actions.send_to_loclist*
|
|
||||||
Sends files in the current oil directory to the location list, replacing the
|
|
||||||
previous entries.
|
|
||||||
|
|
||||||
send_to_qflist *actions.send_to_qflist*
|
send_to_qflist *actions.send_to_qflist*
|
||||||
Sends files in the current oil directory to the quickfix list, replacing the
|
Sends files in the current oil directory to the quickfix list, replacing the
|
||||||
previous entries.
|
previous entries.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
{action} `"r"|"a"` Replace or add to current quickfix list (see
|
||||||
|
|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
|
||||||
|
|
||||||
show_help *actions.show_help*
|
show_help *actions.show_help*
|
||||||
Show default keymaps
|
Show default keymaps
|
||||||
|
|
||||||
tcd *actions.tcd*
|
|
||||||
:tcd to the current oil directory
|
|
||||||
|
|
||||||
toggle_hidden *actions.toggle_hidden*
|
toggle_hidden *actions.toggle_hidden*
|
||||||
Toggle hidden files and directories
|
Toggle hidden files and directories
|
||||||
|
|
||||||
toggle_trash *actions.toggle_trash*
|
toggle_trash *actions.toggle_trash*
|
||||||
Jump to and from the trash for the current directory
|
Jump to and from the trash for the current directory
|
||||||
|
|
||||||
|
yank_entry *actions.yank_entry*
|
||||||
|
Yank the filepath of the entry under the cursor to a register
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
{modify} `string` Modify the path with |fnamemodify()| using this as the
|
||||||
|
mods argument
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
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
|
||||||
|
|
||||||
|
|
@ -552,13 +757,13 @@ 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.
|
||||||
https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html
|
https://specifications.freedesktop.org/trash-spec/1.0/
|
||||||
All features should work.
|
All features should work.
|
||||||
|
|
||||||
Mac:
|
Mac:
|
||||||
|
|
@ -567,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:
|
||||||
|
|
|
||||||
127
doc/recipes.md
Normal file
127
doc/recipes.md
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
# Recipes
|
||||||
|
|
||||||
|
Have a cool recipe to share? Open a pull request and add it to this doc!
|
||||||
|
|
||||||
|
<!-- TOC -->
|
||||||
|
|
||||||
|
- [Toggle file detail view](#toggle-file-detail-view)
|
||||||
|
- [Show CWD in the winbar](#show-cwd-in-the-winbar)
|
||||||
|
- [Hide gitignored files and show git tracked hidden files](#hide-gitignored-files-and-show-git-tracked-hidden-files)
|
||||||
|
|
||||||
|
<!-- /TOC -->
|
||||||
|
|
||||||
|
## Toggle file detail view
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local detail = false
|
||||||
|
require("oil").setup({
|
||||||
|
keymaps = {
|
||||||
|
["gd"] = {
|
||||||
|
desc = "Toggle file detail view",
|
||||||
|
callback = function()
|
||||||
|
detail = not detail
|
||||||
|
if detail then
|
||||||
|
require("oil").set_columns({ "icon", "permissions", "size", "mtime" })
|
||||||
|
else
|
||||||
|
require("oil").set_columns({ "icon" })
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Show CWD in the winbar
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- Declare a global function to retrieve the current directory
|
||||||
|
function _G.get_oil_winbar()
|
||||||
|
local bufnr = vim.api.nvim_win_get_buf(vim.g.statusline_winid)
|
||||||
|
local dir = require("oil").get_current_dir(bufnr)
|
||||||
|
if dir then
|
||||||
|
return vim.fn.fnamemodify(dir, ":~")
|
||||||
|
else
|
||||||
|
-- If there is no current directory (e.g. over ssh), just show the buffer name
|
||||||
|
return vim.api.nvim_buf_get_name(0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
require("oil").setup({
|
||||||
|
win_options = {
|
||||||
|
winbar = "%!v:lua.get_oil_winbar()",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Hide gitignored files and show git tracked hidden files
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- helper function to parse output
|
||||||
|
local function parse_output(proc)
|
||||||
|
local result = proc:wait()
|
||||||
|
local ret = {}
|
||||||
|
if result.code == 0 then
|
||||||
|
for line in vim.gsplit(result.stdout, "\n", { plain = true, trimempty = true }) do
|
||||||
|
-- Remove trailing slash
|
||||||
|
line = line:gsub("/$", "")
|
||||||
|
ret[line] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
|
||||||
|
-- build git status cache
|
||||||
|
local function new_git_status()
|
||||||
|
return setmetatable({}, {
|
||||||
|
__index = function(self, key)
|
||||||
|
local ignore_proc = vim.system(
|
||||||
|
{ "git", "ls-files", "--ignored", "--exclude-standard", "--others", "--directory" },
|
||||||
|
{
|
||||||
|
cwd = key,
|
||||||
|
text = true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
local tracked_proc = vim.system({ "git", "ls-tree", "HEAD", "--name-only" }, {
|
||||||
|
cwd = key,
|
||||||
|
text = true,
|
||||||
|
})
|
||||||
|
local ret = {
|
||||||
|
ignored = parse_output(ignore_proc),
|
||||||
|
tracked = parse_output(tracked_proc),
|
||||||
|
}
|
||||||
|
|
||||||
|
rawset(self, key, ret)
|
||||||
|
return ret
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
local git_status = new_git_status()
|
||||||
|
|
||||||
|
-- Clear git status cache on refresh
|
||||||
|
local refresh = require("oil.actions").refresh
|
||||||
|
local orig_refresh = refresh.callback
|
||||||
|
refresh.callback = function(...)
|
||||||
|
git_status = new_git_status()
|
||||||
|
orig_refresh(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
require("oil").setup({
|
||||||
|
view_options = {
|
||||||
|
is_hidden_file = function(name, bufnr)
|
||||||
|
local dir = require("oil").get_current_dir(bufnr)
|
||||||
|
local is_dotfile = vim.startswith(name, ".") and name ~= ".."
|
||||||
|
-- if no local directory (e.g. for ssh connections), just hide dotfiles
|
||||||
|
if not dir then
|
||||||
|
return is_dotfile
|
||||||
|
end
|
||||||
|
-- dotfiles are considered hidden unless tracked
|
||||||
|
if is_dotfile then
|
||||||
|
return not git_status[dir].tracked[name]
|
||||||
|
else
|
||||||
|
-- Check if file is gitignored
|
||||||
|
return git_status[dir].ignored[name]
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
@ -1,26 +1,51 @@
|
||||||
local oil = require("oil")
|
local oil = require("oil")
|
||||||
local util = require("oil.util")
|
local util = require("oil.util")
|
||||||
|
|
||||||
-- TODO remove after https://github.com/folke/neodev.nvim/pull/163 lands
|
|
||||||
---@diagnostic disable: inject-field
|
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
M.show_help = {
|
M.show_help = {
|
||||||
desc = "Show default keymaps",
|
|
||||||
callback = function()
|
callback = function()
|
||||||
local config = require("oil.config")
|
local config = require("oil.config")
|
||||||
require("oil.keymap_util").show_help(config.keymaps)
|
require("oil.keymap_util").show_help(config.keymaps)
|
||||||
end,
|
end,
|
||||||
|
desc = "Show default keymaps",
|
||||||
}
|
}
|
||||||
|
|
||||||
M.select = {
|
M.select = {
|
||||||
desc = "Open the entry under the cursor",
|
desc = "Open the entry under the cursor",
|
||||||
callback = oil.select,
|
callback = function(opts)
|
||||||
|
opts = opts or {}
|
||||||
|
local callback = opts.callback
|
||||||
|
opts.callback = nil
|
||||||
|
oil.select(opts, callback)
|
||||||
|
end,
|
||||||
|
parameters = {
|
||||||
|
vertical = {
|
||||||
|
type = "boolean",
|
||||||
|
desc = "Open the buffer in a vertical split",
|
||||||
|
},
|
||||||
|
horizontal = {
|
||||||
|
type = "boolean",
|
||||||
|
desc = "Open the buffer in a horizontal split",
|
||||||
|
},
|
||||||
|
split = {
|
||||||
|
type = '"aboveleft"|"belowright"|"topleft"|"botright"',
|
||||||
|
desc = "Split modifier",
|
||||||
|
},
|
||||||
|
tab = {
|
||||||
|
type = "boolean",
|
||||||
|
desc = "Open the buffer in a new tab",
|
||||||
|
},
|
||||||
|
close = {
|
||||||
|
type = "boolean",
|
||||||
|
desc = "Close the original oil buffer once selection is made",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
M.select_vsplit = {
|
M.select_vsplit = {
|
||||||
desc = "Open the entry under the cursor in a vertical split",
|
desc = "Open the entry under the cursor in a vertical split",
|
||||||
|
deprecated = true,
|
||||||
callback = function()
|
callback = function()
|
||||||
oil.select({ vertical = true })
|
oil.select({ vertical = true })
|
||||||
end,
|
end,
|
||||||
|
|
@ -28,6 +53,7 @@ M.select_vsplit = {
|
||||||
|
|
||||||
M.select_split = {
|
M.select_split = {
|
||||||
desc = "Open the entry under the cursor in a horizontal split",
|
desc = "Open the entry under the cursor in a horizontal split",
|
||||||
|
deprecated = true,
|
||||||
callback = function()
|
callback = function()
|
||||||
oil.select({ horizontal = true })
|
oil.select({ horizontal = true })
|
||||||
end,
|
end,
|
||||||
|
|
@ -35,6 +61,7 @@ M.select_split = {
|
||||||
|
|
||||||
M.select_tab = {
|
M.select_tab = {
|
||||||
desc = "Open the entry under the cursor in a new tab",
|
desc = "Open the entry under the cursor in a new tab",
|
||||||
|
deprecated = true,
|
||||||
callback = function()
|
callback = function()
|
||||||
oil.select({ tab = true })
|
oil.select({ tab = true })
|
||||||
end,
|
end,
|
||||||
|
|
@ -42,7 +69,21 @@ M.select_tab = {
|
||||||
|
|
||||||
M.preview = {
|
M.preview = {
|
||||||
desc = "Open the entry under the cursor in a preview window, or close the preview window if already open",
|
desc = "Open the entry under the cursor in a preview window, or close the preview window if already open",
|
||||||
callback = function()
|
parameters = {
|
||||||
|
vertical = {
|
||||||
|
type = "boolean",
|
||||||
|
desc = "Open the buffer in a vertical split",
|
||||||
|
},
|
||||||
|
horizontal = {
|
||||||
|
type = "boolean",
|
||||||
|
desc = "Open the buffer in a horizontal split",
|
||||||
|
},
|
||||||
|
split = {
|
||||||
|
type = '"aboveleft"|"belowright"|"topleft"|"botright"',
|
||||||
|
desc = "Split modifier",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
callback = function(opts)
|
||||||
local entry = oil.get_cursor_entry()
|
local entry = oil.get_cursor_entry()
|
||||||
if not entry then
|
if not entry then
|
||||||
vim.notify("Could not find entry under cursor", vim.log.levels.ERROR)
|
vim.notify("Could not find entry under cursor", vim.log.levels.ERROR)
|
||||||
|
|
@ -53,10 +94,15 @@ M.preview = {
|
||||||
local cur_id = vim.w[winid].oil_entry_id
|
local cur_id = vim.w[winid].oil_entry_id
|
||||||
if entry.id == cur_id then
|
if entry.id == cur_id then
|
||||||
vim.api.nvim_win_close(winid, true)
|
vim.api.nvim_win_close(winid, true)
|
||||||
|
if util.is_floating_win() then
|
||||||
|
local layout = require("oil.layout")
|
||||||
|
local win_opts = layout.get_fullscreen_win_opts()
|
||||||
|
vim.api.nvim_win_set_config(0, win_opts)
|
||||||
|
end
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
oil.select({ preview = true })
|
oil.open_preview(opts)
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,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,
|
||||||
|
|
@ -97,14 +167,27 @@ 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
|
||||||
local function cd(cmd)
|
---@param silent? boolean
|
||||||
|
local function cd(cmd, silent)
|
||||||
local dir = oil.get_current_dir()
|
local dir = oil.get_current_dir()
|
||||||
if dir then
|
if dir then
|
||||||
vim.cmd({ cmd = cmd, args = { dir } })
|
vim.cmd({ cmd = cmd, args = { dir } })
|
||||||
|
if not silent then
|
||||||
|
vim.notify(string.format("CWD: %s", dir), vim.log.levels.INFO)
|
||||||
|
end
|
||||||
else
|
else
|
||||||
vim.notify("Cannot :cd; not in a directory", vim.log.levels.WARN)
|
vim.notify("Cannot :cd; not in a directory", vim.log.levels.WARN)
|
||||||
end
|
end
|
||||||
|
|
@ -112,13 +195,31 @@ end
|
||||||
|
|
||||||
M.cd = {
|
M.cd = {
|
||||||
desc = ":cd to the current oil directory",
|
desc = ":cd to the current oil directory",
|
||||||
callback = function()
|
callback = function(opts)
|
||||||
cd("cd")
|
opts = opts or {}
|
||||||
|
local cmd = "cd"
|
||||||
|
if opts.scope == "tab" then
|
||||||
|
cmd = "tcd"
|
||||||
|
elseif opts.scope == "win" then
|
||||||
|
cmd = "lcd"
|
||||||
|
end
|
||||||
|
cd(cmd, opts.silent)
|
||||||
end,
|
end,
|
||||||
|
parameters = {
|
||||||
|
scope = {
|
||||||
|
type = 'nil|"tab"|"win"',
|
||||||
|
desc = "Scope of the directory change (e.g. use |:tcd| or |:lcd|)",
|
||||||
|
},
|
||||||
|
silent = {
|
||||||
|
type = "boolean",
|
||||||
|
desc = "Do not show a message when changing directories",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
M.tcd = {
|
M.tcd = {
|
||||||
desc = ":tcd to the current oil directory",
|
desc = ":tcd to the current oil directory",
|
||||||
|
deprecated = true,
|
||||||
callback = function()
|
callback = function()
|
||||||
cd("tcd")
|
cd("tcd")
|
||||||
end,
|
end,
|
||||||
|
|
@ -152,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)
|
||||||
vim.fn.termopen(vim.o.shell, { cwd = dir })
|
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 })
|
||||||
|
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
|
||||||
|
|
@ -202,8 +314,12 @@ M.open_external = {
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local path = dir .. entry.name
|
local path = dir .. entry.name
|
||||||
-- TODO use vim.ui.open once this is resolved
|
|
||||||
-- https://github.com/neovim/neovim/issues/24567
|
if vim.ui.open then
|
||||||
|
vim.ui.open(path)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
local cmd, err = get_open_cmd(path)
|
local cmd, err = get_open_cmd(path)
|
||||||
if not cmd then
|
if not cmd then
|
||||||
vim.notify(string.format("Could not open %s: %s", path, err), vim.log.levels.ERROR)
|
vim.notify(string.format("Could not open %s: %s", path, err), vim.log.levels.ERROR)
|
||||||
|
|
@ -216,8 +332,9 @@ M.open_external = {
|
||||||
|
|
||||||
M.refresh = {
|
M.refresh = {
|
||||||
desc = "Refresh current directory list",
|
desc = "Refresh current directory list",
|
||||||
callback = function()
|
callback = function(opts)
|
||||||
if vim.bo.modified then
|
opts = opts or {}
|
||||||
|
if vim.bo.modified and not opts.force then
|
||||||
local ok, choice = pcall(vim.fn.confirm, "Discard changes?", "No\nYes")
|
local ok, choice = pcall(vim.fn.confirm, "Discard changes?", "No\nYes")
|
||||||
if not ok or choice ~= 2 then
|
if not ok or choice ~= 2 then
|
||||||
return
|
return
|
||||||
|
|
@ -228,6 +345,12 @@ M.refresh = {
|
||||||
-- :h CTRL-L-default
|
-- :h CTRL-L-default
|
||||||
vim.cmd.nohlsearch()
|
vim.cmd.nohlsearch()
|
||||||
end,
|
end,
|
||||||
|
parameters = {
|
||||||
|
force = {
|
||||||
|
desc = "When true, do not prompt user if they will be discarding changes",
|
||||||
|
type = "boolean",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
local function open_cmdline_with_path(path)
|
local function open_cmdline_with_path(path)
|
||||||
|
|
@ -238,7 +361,10 @@ end
|
||||||
|
|
||||||
M.open_cmdline = {
|
M.open_cmdline = {
|
||||||
desc = "Open vim cmdline with current entry as an argument",
|
desc = "Open vim cmdline with current entry as an argument",
|
||||||
callback = function()
|
callback = function(opts)
|
||||||
|
opts = vim.tbl_deep_extend("keep", opts or {}, {
|
||||||
|
shorten_path = true,
|
||||||
|
})
|
||||||
local config = require("oil.config")
|
local config = require("oil.config")
|
||||||
local fs = require("oil.fs")
|
local fs = require("oil.fs")
|
||||||
local entry = oil.get_cursor_entry()
|
local entry = oil.get_cursor_entry()
|
||||||
|
|
@ -254,13 +380,57 @@ M.open_cmdline = {
|
||||||
if not adapter or not path or adapter.name ~= "files" then
|
if not adapter or not path or adapter.name ~= "files" then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local fullpath = fs.shorten_path(fs.posix_to_os_path(path) .. entry.name)
|
local fullpath = fs.posix_to_os_path(path) .. entry.name
|
||||||
|
if opts.modify then
|
||||||
|
fullpath = vim.fn.fnamemodify(fullpath, opts.modify)
|
||||||
|
end
|
||||||
|
if opts.shorten_path then
|
||||||
|
fullpath = fs.shorten_path(fullpath)
|
||||||
|
end
|
||||||
open_cmdline_with_path(fullpath)
|
open_cmdline_with_path(fullpath)
|
||||||
end,
|
end,
|
||||||
|
parameters = {
|
||||||
|
modify = {
|
||||||
|
desc = "Modify the path with |fnamemodify()| using this as the mods argument",
|
||||||
|
type = "string",
|
||||||
|
},
|
||||||
|
shorten_path = {
|
||||||
|
desc = "Use relative paths when possible",
|
||||||
|
type = "boolean",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
M.yank_entry = {
|
||||||
|
desc = "Yank the filepath of the entry under the cursor to a register",
|
||||||
|
callback = function(opts)
|
||||||
|
opts = opts or {}
|
||||||
|
local entry = oil.get_cursor_entry()
|
||||||
|
local dir = oil.get_current_dir()
|
||||||
|
if not entry or not dir then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local name = entry.name
|
||||||
|
if entry.type == "directory" then
|
||||||
|
name = name .. "/"
|
||||||
|
end
|
||||||
|
local path = dir .. name
|
||||||
|
if opts.modify then
|
||||||
|
path = vim.fn.fnamemodify(path, opts.modify)
|
||||||
|
end
|
||||||
|
vim.fn.setreg(vim.v.register, path)
|
||||||
|
end,
|
||||||
|
parameters = {
|
||||||
|
modify = {
|
||||||
|
desc = "Modify the path with |fnamemodify()| using this as the mods argument",
|
||||||
|
type = "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
M.copy_entry_path = {
|
M.copy_entry_path = {
|
||||||
desc = "Yank the filepath of the entry under the cursor to a register",
|
desc = "Yank the filepath of the entry under the cursor to a register",
|
||||||
|
deprecated = true,
|
||||||
callback = function()
|
callback = function()
|
||||||
local entry = oil.get_cursor_entry()
|
local entry = oil.get_cursor_entry()
|
||||||
local dir = oil.get_current_dir()
|
local dir = oil.get_current_dir()
|
||||||
|
|
@ -271,8 +441,41 @@ M.copy_entry_path = {
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
M.copy_entry_filename = {
|
||||||
|
desc = "Yank the filename of the entry under the cursor to a register",
|
||||||
|
deprecated = true,
|
||||||
|
callback = function()
|
||||||
|
local entry = oil.get_cursor_entry()
|
||||||
|
if not entry then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
vim.fn.setreg(vim.v.register, entry.name)
|
||||||
|
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,
|
||||||
callback = function()
|
callback = function()
|
||||||
local fs = require("oil.fs")
|
local fs = require("oil.fs")
|
||||||
local dir = oil.get_current_dir()
|
local dir = oil.get_current_dir()
|
||||||
|
|
@ -284,7 +487,14 @@ M.open_cmdline_dir = {
|
||||||
|
|
||||||
M.change_sort = {
|
M.change_sort = {
|
||||||
desc = "Change the sort order",
|
desc = "Change the sort order",
|
||||||
callback = function()
|
callback = function(opts)
|
||||||
|
opts = opts or {}
|
||||||
|
|
||||||
|
if opts.sort then
|
||||||
|
oil.set_sort(opts.sort)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
local sort_cols = { "name", "size", "atime", "mtime", "ctime", "birthtime" }
|
local sort_cols = { "name", "size", "atime", "mtime", "ctime", "birthtime" }
|
||||||
vim.ui.select(sort_cols, { prompt = "Sort by", kind = "oil_sort_col" }, function(col)
|
vim.ui.select(sort_cols, { prompt = "Sort by", kind = "oil_sort_col" }, function(col)
|
||||||
if not col then
|
if not col then
|
||||||
|
|
@ -306,6 +516,12 @@ M.change_sort = {
|
||||||
)
|
)
|
||||||
end)
|
end)
|
||||||
end,
|
end,
|
||||||
|
parameters = {
|
||||||
|
sort = {
|
||||||
|
type = "oil.SortSpec[]",
|
||||||
|
desc = "List of columns plus direction (see |oil.set_sort|) instead of interactive selection",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
M.toggle_trash = {
|
M.toggle_trash = {
|
||||||
|
|
@ -339,16 +555,37 @@ M.toggle_trash = {
|
||||||
|
|
||||||
M.send_to_qflist = {
|
M.send_to_qflist = {
|
||||||
desc = "Sends files in the current oil directory to the quickfix list, replacing the previous entries.",
|
desc = "Sends files in the current oil directory to the quickfix list, replacing the previous entries.",
|
||||||
callback = function()
|
callback = function(opts)
|
||||||
util.send_to_quickfix({
|
opts = vim.tbl_deep_extend("keep", opts or {}, {
|
||||||
target = "qflist",
|
target = "qflist",
|
||||||
mode = "r",
|
action = "r",
|
||||||
|
only_matching_search = false,
|
||||||
|
})
|
||||||
|
util.send_to_quickfix({
|
||||||
|
target = opts.target,
|
||||||
|
action = opts.action,
|
||||||
|
only_matching_search = opts.only_matching_search,
|
||||||
})
|
})
|
||||||
end,
|
end,
|
||||||
|
parameters = {
|
||||||
|
target = {
|
||||||
|
type = '"qflist"|"loclist"',
|
||||||
|
desc = "The target list to send files to",
|
||||||
|
},
|
||||||
|
action = {
|
||||||
|
type = '"r"|"a"',
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
M.add_to_qflist = {
|
M.add_to_qflist = {
|
||||||
desc = "Adds files in the current oil directory to the quickfix list, keeping the previous entries.",
|
desc = "Adds files in the current oil directory to the quickfix list, keeping the previous entries.",
|
||||||
|
deprecated = true,
|
||||||
callback = function()
|
callback = function()
|
||||||
util.send_to_quickfix({
|
util.send_to_quickfix({
|
||||||
target = "qflist",
|
target = "qflist",
|
||||||
|
|
@ -359,6 +596,7 @@ M.add_to_qflist = {
|
||||||
|
|
||||||
M.send_to_loclist = {
|
M.send_to_loclist = {
|
||||||
desc = "Sends files in the current oil directory to the location list, replacing the previous entries.",
|
desc = "Sends files in the current oil directory to the location list, replacing the previous entries.",
|
||||||
|
deprecated = true,
|
||||||
callback = function()
|
callback = function()
|
||||||
util.send_to_quickfix({
|
util.send_to_quickfix({
|
||||||
target = "loclist",
|
target = "loclist",
|
||||||
|
|
@ -369,6 +607,7 @@ M.send_to_loclist = {
|
||||||
|
|
||||||
M.add_to_loclist = {
|
M.add_to_loclist = {
|
||||||
desc = "Adds files in the current oil directory to the location list, keeping the previous entries.",
|
desc = "Adds files in the current oil directory to the location list, keeping the previous entries.",
|
||||||
|
deprecated = true,
|
||||||
callback = function()
|
callback = function()
|
||||||
util.send_to_quickfix({
|
util.send_to_quickfix({
|
||||||
target = "loclist",
|
target = "loclist",
|
||||||
|
|
@ -386,6 +625,8 @@ M._get_actions = function()
|
||||||
table.insert(ret, {
|
table.insert(ret, {
|
||||||
name = name,
|
name = name,
|
||||||
desc = action.desc,
|
desc = action.desc,
|
||||||
|
deprecated = action.deprecated,
|
||||||
|
parameters = action.parameters,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,16 @@ local columns = require("oil.columns")
|
||||||
local config = require("oil.config")
|
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 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)
|
||||||
|
|
@ -33,34 +35,28 @@ local function read_link_data(path, cb)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@class (exact) oil.FilesAdapter: oil.Adapter
|
||||||
|
---@field to_short_os_path fun(path: string, entry_type: nil|oil.EntryType): string
|
||||||
|
|
||||||
---@param path string
|
---@param path string
|
||||||
---@param entry_type nil|oil.EntryType
|
---@param entry_type nil|oil.EntryType
|
||||||
---@return string
|
---@return string
|
||||||
M.to_short_os_path = function(path, entry_type)
|
M.to_short_os_path = function(path, entry_type)
|
||||||
local shortpath = fs.shorten_path(fs.posix_to_os_path(path))
|
local shortpath = fs.shorten_path(fs.posix_to_os_path(path))
|
||||||
if entry_type == "directory" then
|
if entry_type == "directory" then
|
||||||
shortpath = util.addslash(shortpath)
|
shortpath = util.addslash(shortpath, true)
|
||||||
end
|
end
|
||||||
return shortpath
|
return shortpath
|
||||||
end
|
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
|
||||||
|
|
@ -77,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
|
||||||
|
|
@ -93,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
|
||||||
|
|
@ -110,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
|
||||||
|
|
@ -156,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
|
||||||
|
|
@ -183,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
|
||||||
|
|
@ -192,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
|
||||||
|
|
@ -202,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)
|
||||||
|
|
@ -213,19 +236,36 @@ end
|
||||||
M.normalize_url = function(url, callback)
|
M.normalize_url = function(url, callback)
|
||||||
local scheme, path = util.parse_url(url)
|
local scheme, path = util.parse_url(url)
|
||||||
assert(path)
|
assert(path)
|
||||||
if fs.is_windows and path == "/" then
|
|
||||||
return callback(url)
|
if fs.is_windows then
|
||||||
|
if path == "/" then
|
||||||
|
return callback(url)
|
||||||
|
else
|
||||||
|
local is_root_drive = path:match("^/%u$")
|
||||||
|
if is_root_drive then
|
||||||
|
return callback(url .. "/")
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local os_path = vim.fn.fnamemodify(fs.posix_to_os_path(path), ":p")
|
local os_path = vim.fn.fnamemodify(fs.posix_to_os_path(path), ":p")
|
||||||
uv.fs_realpath(os_path, function(err, new_os_path)
|
uv.fs_realpath(os_path, function(err, new_os_path)
|
||||||
local realpath = new_os_path or os_path
|
local realpath
|
||||||
|
if fs.is_windows then
|
||||||
|
-- Ignore the fs_realpath on windows because it will resolve mapped network drives to the IP
|
||||||
|
-- address instead of using the drive letter
|
||||||
|
realpath = os_path
|
||||||
|
else
|
||||||
|
realpath = new_os_path or os_path
|
||||||
|
end
|
||||||
|
|
||||||
uv.fs_stat(
|
uv.fs_stat(
|
||||||
realpath,
|
realpath,
|
||||||
vim.schedule_wrap(function(stat_err, stat)
|
vim.schedule_wrap(function(stat_err, stat)
|
||||||
local is_directory
|
local is_directory
|
||||||
if stat then
|
if stat then
|
||||||
is_directory = stat.type == "directory"
|
is_directory = stat.type == "directory"
|
||||||
elseif vim.endswith(realpath, "/") then
|
elseif vim.endswith(realpath, "/") or (fs.is_windows and vim.endswith(realpath, "\\")) then
|
||||||
is_directory = true
|
is_directory = true
|
||||||
else
|
else
|
||||||
local filetype = vim.filetype.match({ filename = vim.fs.basename(realpath) })
|
local filetype = vim.filetype.match({ filename = vim.fs.basename(realpath) })
|
||||||
|
|
@ -236,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)
|
||||||
)
|
)
|
||||||
|
|
@ -262,11 +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)
|
||||||
local fetch_meta = columns.get_metadata_fetcher(M, column_defs)
|
local _, path = util.parse_url(url)
|
||||||
|
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,
|
||||||
|
|
@ -296,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)
|
table.insert(internal_entries, cache_entry)
|
||||||
if err then
|
fetch_entry_metadata(path, cache_entry, require_stat, complete_disk_cb)
|
||||||
complete_disk_cb(err)
|
|
||||||
else
|
|
||||||
table.insert(internal_entries, cache_entry)
|
|
||||||
complete_disk_cb()
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
@ -323,9 +448,9 @@ 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)
|
||||||
local fetch_meta = columns.get_metadata_fetcher(M, column_defs)
|
local require_stat = columns_require_stat(column_defs)
|
||||||
|
|
||||||
---@diagnostic disable-next-line: param-type-mismatch
|
---@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)
|
||||||
if open_err then
|
if open_err then
|
||||||
if open_err:match("^ENOENT: no such file or directory") then
|
if open_err:match("^ENOENT: no such file or directory") then
|
||||||
|
|
@ -355,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)
|
fetch_entry_metadata(path, cache_entry, require_stat, poll)
|
||||||
local meta = cache_entry[FIELD_META]
|
|
||||||
-- 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)
|
||||||
|
|
@ -409,22 +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 gid = uv.getgid()
|
|
||||||
local rwx
|
|
||||||
if uid == stat.uid then
|
|
||||||
rwx = bit.rshift(stat.mode, 6)
|
|
||||||
elseif gid == stat.gid then
|
|
||||||
rwx = bit.rshift(stat.mode, 3)
|
|
||||||
else
|
|
||||||
rwx = stat.mode
|
|
||||||
end
|
|
||||||
return bit.band(rwx, 2) ~= 0
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param action oil.Action
|
---@param action oil.Action
|
||||||
|
|
@ -448,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)
|
||||||
|
|
@ -476,6 +567,18 @@ M.perform_action = function(action, cb)
|
||||||
local _, path = util.parse_url(action.url)
|
local _, path = util.parse_url(action.url)
|
||||||
assert(path)
|
assert(path)
|
||||||
path = fs.posix_to_os_path(path)
|
path = fs.posix_to_os_path(path)
|
||||||
|
|
||||||
|
if config.git.add(path) then
|
||||||
|
local old_cb = cb
|
||||||
|
cb = vim.schedule_wrap(function(err)
|
||||||
|
if not err then
|
||||||
|
git.add(path, old_cb)
|
||||||
|
else
|
||||||
|
old_cb(err)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
if action.entry_type == "directory" then
|
if action.entry_type == "directory" then
|
||||||
uv.fs_mkdir(path, 493, function(err)
|
uv.fs_mkdir(path, 493, function(err)
|
||||||
-- Ignore if the directory already exists
|
-- Ignore if the directory already exists
|
||||||
|
|
@ -503,21 +606,25 @@ M.perform_action = function(action, cb)
|
||||||
local _, path = util.parse_url(action.url)
|
local _, path = util.parse_url(action.url)
|
||||||
assert(path)
|
assert(path)
|
||||||
path = fs.posix_to_os_path(path)
|
path = fs.posix_to_os_path(path)
|
||||||
|
|
||||||
|
if config.git.rm(path) then
|
||||||
|
local old_cb = cb
|
||||||
|
cb = vim.schedule_wrap(function(err)
|
||||||
|
if not err then
|
||||||
|
git.rm(path, old_cb)
|
||||||
|
else
|
||||||
|
old_cb(err)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
if config.delete_to_trash then
|
if config.delete_to_trash then
|
||||||
if config.trash_command then
|
require("oil.adapters.trash").delete_to_trash(path, cb)
|
||||||
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)
|
|
||||||
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)
|
||||||
|
|
@ -525,13 +632,17 @@ M.perform_action = function(action, cb)
|
||||||
assert(dest_path)
|
assert(dest_path)
|
||||||
src_path = fs.posix_to_os_path(src_path)
|
src_path = fs.posix_to_os_path(src_path)
|
||||||
dest_path = fs.posix_to_os_path(dest_path)
|
dest_path = fs.posix_to_os_path(dest_path)
|
||||||
fs.recursive_move(action.entry_type, src_path, dest_path, cb)
|
if config.git.mv(src_path, dest_path) then
|
||||||
|
git.mv(action.entry_type, src_path, dest_path, cb)
|
||||||
|
else
|
||||||
|
fs.recursive_move(action.entry_type, src_path, dest_path, cb)
|
||||||
|
end
|
||||||
else
|
else
|
||||||
-- We should never hit this because we don't implement supported_cross_adapter_actions
|
-- We should never hit this because we don't implement supported_cross_adapter_actions
|
||||||
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
|
||||||
|
|
@ -20,6 +20,13 @@ local FIELD_META = constants.FIELD_META
|
||||||
---@field port nil|integer
|
---@field port nil|integer
|
||||||
---@field path string
|
---@field path string
|
||||||
|
|
||||||
|
---@param args string[]
|
||||||
|
local function scp(args, ...)
|
||||||
|
local cmd = vim.list_extend({ "scp", "-C" }, config.extra_scp_args)
|
||||||
|
vim.list_extend(cmd, args)
|
||||||
|
shell.run(cmd, ...)
|
||||||
|
end
|
||||||
|
|
||||||
---@param oil_url string
|
---@param oil_url string
|
||||||
---@return oil.sshUrl
|
---@return oil.sshUrl
|
||||||
M.parse_url = function(oil_url)
|
M.parse_url = function(oil_url)
|
||||||
|
|
@ -43,6 +50,7 @@ M.parse_url = function(oil_url)
|
||||||
error(string.format("Malformed SSH url: %s", oil_url))
|
error(string.format("Malformed SSH url: %s", oil_url))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@cast ret oil.sshUrl
|
||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -118,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
|
||||||
|
|
@ -161,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
|
||||||
|
|
@ -295,15 +303,15 @@ 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)
|
||||||
local src_conn = get_connection(action.src_url)
|
local src_conn = get_connection(action.src_url)
|
||||||
local dest_conn = get_connection(action.dest_url)
|
local dest_conn = get_connection(action.dest_url)
|
||||||
if src_conn ~= dest_conn then
|
if src_conn ~= dest_conn then
|
||||||
shell.run({ "scp", "-C", "-r", url_to_scp(src_res), url_to_scp(dest_res) }, function(err)
|
scp({ "-r", url_to_scp(src_res), url_to_scp(dest_res) }, function(err)
|
||||||
if err then
|
if err then
|
||||||
return cb(err)
|
return cb(err)
|
||||||
end
|
end
|
||||||
|
|
@ -316,13 +324,13 @@ 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)
|
||||||
if not url_hosts_equal(src_res, dest_res) then
|
if not url_hosts_equal(src_res, dest_res) then
|
||||||
shell.run({ "scp", "-C", "-r", url_to_scp(src_res), url_to_scp(dest_res) }, cb)
|
scp({ "-r", url_to_scp(src_res), url_to_scp(dest_res) }, cb)
|
||||||
else
|
else
|
||||||
local src_conn = get_connection(action.src_url)
|
local src_conn = get_connection(action.src_url)
|
||||||
src_conn:cp(src_res.path, dest_res.path, cb)
|
src_conn:cp(src_res.path, dest_res.path, cb)
|
||||||
|
|
@ -341,7 +349,7 @@ M.perform_action = function(action, cb)
|
||||||
src_arg = fs.posix_to_os_path(path)
|
src_arg = fs.posix_to_os_path(path)
|
||||||
dest_arg = url_to_scp(M.parse_url(action.dest_url))
|
dest_arg = url_to_scp(M.parse_url(action.dest_url))
|
||||||
end
|
end
|
||||||
shell.run({ "scp", "-C", "-r", src_arg, dest_arg }, cb)
|
scp({ "-r", src_arg, dest_arg }, cb)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
cb(string.format("Bad action type: %s", action.type))
|
cb(string.format("Bad action type: %s", action.type))
|
||||||
|
|
@ -357,7 +365,9 @@ M.read_file = function(bufnr)
|
||||||
local url = M.parse_url(bufname)
|
local url = M.parse_url(bufname)
|
||||||
local scp_url = url_to_scp(url)
|
local scp_url = url_to_scp(url)
|
||||||
local basename = pathutil.basename(bufname)
|
local basename = pathutil.basename(bufname)
|
||||||
local tmpdir = fs.join(vim.fn.stdpath("cache"), "oil")
|
local cache_dir = vim.fn.stdpath("cache")
|
||||||
|
assert(type(cache_dir) == "string")
|
||||||
|
local tmpdir = fs.join(cache_dir, "oil")
|
||||||
fs.mkdirp(tmpdir)
|
fs.mkdirp(tmpdir)
|
||||||
local fd, tmpfile = vim.loop.fs_mkstemp(fs.join(tmpdir, "ssh_XXXXXX"))
|
local fd, tmpfile = vim.loop.fs_mkstemp(fs.join(tmpdir, "ssh_XXXXXX"))
|
||||||
if fd then
|
if fd then
|
||||||
|
|
@ -365,7 +375,7 @@ M.read_file = function(bufnr)
|
||||||
end
|
end
|
||||||
local tmp_bufnr = vim.fn.bufadd(tmpfile)
|
local tmp_bufnr = vim.fn.bufadd(tmpfile)
|
||||||
|
|
||||||
shell.run({ "scp", "-C", scp_url, tmpfile }, function(err)
|
scp({ scp_url, tmpfile }, function(err)
|
||||||
loading.set_loading(bufnr, false)
|
loading.set_loading(bufnr, false)
|
||||||
vim.bo[bufnr].modifiable = true
|
vim.bo[bufnr].modifiable = true
|
||||||
vim.cmd.doautocmd({ args = { "BufReadPre", bufname }, mods = { silent = true } })
|
vim.cmd.doautocmd({ args = { "BufReadPre", bufname }, mods = { silent = true } })
|
||||||
|
|
@ -395,7 +405,9 @@ M.write_file = function(bufnr)
|
||||||
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
||||||
local url = M.parse_url(bufname)
|
local url = M.parse_url(bufname)
|
||||||
local scp_url = url_to_scp(url)
|
local scp_url = url_to_scp(url)
|
||||||
local tmpdir = fs.join(vim.fn.stdpath("cache"), "oil")
|
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, "ssh_XXXXXXXX"))
|
local fd, tmpfile = vim.loop.fs_mkstemp(fs.join(tmpdir, "ssh_XXXXXXXX"))
|
||||||
if fd then
|
if fd then
|
||||||
vim.loop.fs_close(fd)
|
vim.loop.fs_close(fd)
|
||||||
|
|
@ -405,7 +417,7 @@ M.write_file = function(bufnr)
|
||||||
vim.cmd.write({ args = { tmpfile }, bang = true, mods = { silent = true, noautocmd = true } })
|
vim.cmd.write({ args = { tmpfile }, bang = true, mods = { silent = true, noautocmd = true } })
|
||||||
local tmp_bufnr = vim.fn.bufadd(tmpfile)
|
local tmp_bufnr = vim.fn.bufadd(tmpfile)
|
||||||
|
|
||||||
shell.run({ "scp", "-C", tmpfile, scp_url }, function(err)
|
scp({ tmpfile, scp_url }, function(err)
|
||||||
vim.bo[bufnr].modifiable = true
|
vim.bo[bufnr].modifiable = true
|
||||||
if err then
|
if err then
|
||||||
vim.notify(string.format("Error writing file: %s", err), vim.log.levels.ERROR)
|
vim.notify(string.format("Error writing file: %s", err), vim.log.levels.ERROR)
|
||||||
|
|
@ -429,6 +441,7 @@ M.goto_file = function()
|
||||||
url.path = vim.fs.dirname(fullpath)
|
url.path = vim.fs.dirname(fullpath)
|
||||||
local parurl = url_to_str(url)
|
local parurl = url_to_str(url)
|
||||||
|
|
||||||
|
---@cast M oil.Adapter
|
||||||
util.adapter_list_all(M, parurl, {}, function(err, entries)
|
util.adapter_list_all(M, parurl, {}, function(err, entries)
|
||||||
if err then
|
if err then
|
||||||
vim.notify(string.format("Error finding file '%s': %s", fname, err), vim.log.levels.ERROR)
|
vim.notify(string.format("Error finding file '%s': %s", fname, err), vim.log.levels.ERROR)
|
||||||
|
|
|
||||||
|
|
@ -156,7 +156,7 @@ function SSHConnection.new(url)
|
||||||
else
|
else
|
||||||
self.jid = jid
|
self.jid = jid
|
||||||
end
|
end
|
||||||
self:run("whoami", function(err, lines)
|
self:run("id -u", function(err, lines)
|
||||||
if err then
|
if err then
|
||||||
vim.notify(string.format("Error fetching ssh connection user: %s", err), vim.log.levels.WARN)
|
vim.notify(string.format("Error fetching ssh connection user: %s", err), vim.log.levels.WARN)
|
||||||
else
|
else
|
||||||
|
|
@ -164,7 +164,7 @@ function SSHConnection.new(url)
|
||||||
self.meta.user = vim.trim(table.concat(lines, ""))
|
self.meta.user = vim.trim(table.concat(lines, ""))
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
self:run("groups", function(err, lines)
|
self:run("id -G", function(err, lines)
|
||||||
if err then
|
if err then
|
||||||
vim.notify(
|
vim.notify(
|
||||||
string.format("Error fetching ssh connection user groups: %s", err),
|
string.format("Error fetching ssh connection user groups: %s", err),
|
||||||
|
|
@ -176,6 +176,7 @@ function SSHConnection.new(url)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
---@cast self oil.sshConnection
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ local typechar_map = {
|
||||||
---@return table Metadata for entry
|
---@return table Metadata for entry
|
||||||
local function parse_ls_line(line)
|
local function parse_ls_line(line)
|
||||||
local typechar, perms, refcount, user, group, rem =
|
local typechar, perms, refcount, user, group, rem =
|
||||||
line:match("^(.)(%S+)%s+(%d+)%s+(%S+)%s+(%S+)%s+(.*)$")
|
line:match("^(.)(%S+)%s+(%d+)%s+(%d+)%s+(%d+)%s+(.*)$")
|
||||||
if not typechar then
|
if not typechar then
|
||||||
error(string.format("Could not parse '%s'", line))
|
error(string.format("Could not parse '%s'", line))
|
||||||
end
|
end
|
||||||
|
|
@ -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
|
||||||
|
|
@ -70,6 +77,7 @@ end
|
||||||
---@param url oil.sshUrl
|
---@param url oil.sshUrl
|
||||||
---@return oil.sshFs
|
---@return oil.sshFs
|
||||||
function SSHFS.new(url)
|
function SSHFS.new(url)
|
||||||
|
---@type oil.sshFs
|
||||||
return setmetatable({
|
return setmetatable({
|
||||||
conn = SSHConnection.new(url),
|
conn = SSHConnection.new(url),
|
||||||
}, {
|
}, {
|
||||||
|
|
@ -112,7 +120,7 @@ function SSHFS:realpath(path, callback)
|
||||||
abspath = abspath:sub(1, #abspath - 1)
|
abspath = abspath:sub(1, #abspath - 1)
|
||||||
end
|
end
|
||||||
self.conn:run(
|
self.conn:run(
|
||||||
string.format("ls -ald --color=never %s", shellescape(abspath)),
|
string.format("LC_ALL=C ls -land --color=never %s", shellescape(abspath)),
|
||||||
function(ls_err, ls_lines)
|
function(ls_err, ls_lines)
|
||||||
local type
|
local type
|
||||||
if ls_err then
|
if ls_err then
|
||||||
|
|
@ -142,7 +150,7 @@ function SSHFS:list_dir(url, path, callback)
|
||||||
if path ~= "" then
|
if path ~= "" then
|
||||||
path_postfix = string.format(" %s", shellescape(path))
|
path_postfix = string.format(" %s", shellescape(path))
|
||||||
end
|
end
|
||||||
self.conn:run("LANG=C ls -al --color=never" .. path_postfix, function(err, lines)
|
self.conn:run("LC_ALL=C ls -lan --color=never" .. path_postfix, function(err, lines)
|
||||||
if err then
|
if err then
|
||||||
if err:match("No such file or directory%s*$") then
|
if err:match("No such file or directory%s*$") then
|
||||||
-- If the directory doesn't exist, treat the list as a success. We will be able to traverse
|
-- If the directory doesn't exist, treat the list as a success. We will be able to traverse
|
||||||
|
|
@ -175,28 +183,31 @@ function SSHFS:list_dir(url, path, callback)
|
||||||
if any_links then
|
if any_links then
|
||||||
-- If there were any soft links, then we need to run another ls command with -L so that we can
|
-- If there were any soft links, then we need to run another ls command with -L so that we can
|
||||||
-- resolve the type of the link target
|
-- resolve the type of the link target
|
||||||
self.conn:run("ls -aLl --color=never" .. path_postfix, function(link_err, link_lines)
|
self.conn:run(
|
||||||
-- Ignore exit code 1. That just means one of the links could not be resolved.
|
"LC_ALL=C ls -naLl --color=never" .. path_postfix .. " 2> /dev/null",
|
||||||
if link_err and not link_err:match("^1:") then
|
function(link_err, link_lines)
|
||||||
return callback(link_err)
|
-- Ignore exit code 1. That just means one of the links could not be resolved.
|
||||||
end
|
if link_err and not link_err:match("^1:") then
|
||||||
assert(link_lines)
|
return callback(link_err)
|
||||||
for _, line in ipairs(link_lines) do
|
end
|
||||||
if line ~= "" and not line:match("^total") then
|
assert(link_lines)
|
||||||
local ok, name, type, meta = pcall(parse_ls_line, line)
|
for _, line in ipairs(link_lines) do
|
||||||
if ok and name ~= "." and name ~= ".." then
|
if line ~= "" and not line:match("^total") then
|
||||||
local cache_entry = entries[name]
|
local ok, name, type, meta = pcall(parse_ls_line, line)
|
||||||
if cache_entry[FIELD_TYPE] == "link" then
|
if ok and name ~= "." and name ~= ".." then
|
||||||
cache_entry[FIELD_META].link_stat = {
|
local cache_entry = entries[name]
|
||||||
type = type,
|
if cache_entry[FIELD_TYPE] == "link" then
|
||||||
size = meta.size,
|
cache_entry[FIELD_META].link_stat = {
|
||||||
}
|
type = type,
|
||||||
|
size = meta.size,
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
callback(nil, cache_entries)
|
||||||
end
|
end
|
||||||
callback(nil, cache_entries)
|
)
|
||||||
end)
|
|
||||||
else
|
else
|
||||||
callback(nil, cache_entries)
|
callback(nil, cache_entries)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
-- Based on the FreeDesktop.org trash specification
|
-- Based on the FreeDesktop.org trash specification
|
||||||
-- https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html
|
-- https://specifications.freedesktop.org/trash-spec/1.0/
|
||||||
local cache = require("oil.cache")
|
local cache = require("oil.cache")
|
||||||
local config = require("oil.config")
|
local config = require("oil.config")
|
||||||
local constants = require("oil.constants")
|
local constants = require("oil.constants")
|
||||||
|
|
@ -75,8 +75,13 @@ end
|
||||||
---@param path string
|
---@param path string
|
||||||
---@return string
|
---@return string
|
||||||
local function get_write_trash_dir(path)
|
local function get_write_trash_dir(path)
|
||||||
local dev = uv.fs_lstat(path).dev
|
local lstat = uv.fs_lstat(path)
|
||||||
local home_trash = get_home_trash_dir()
|
local home_trash = get_home_trash_dir()
|
||||||
|
if not lstat then
|
||||||
|
-- If the source file doesn't exist default to home trash dir
|
||||||
|
return home_trash
|
||||||
|
end
|
||||||
|
local dev = lstat.dev
|
||||||
if uv.fs_lstat(home_trash).dev == dev then
|
if uv.fs_lstat(home_trash).dev == dev then
|
||||||
return home_trash
|
return home_trash
|
||||||
end
|
end
|
||||||
|
|
@ -126,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
|
||||||
|
|
@ -146,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)
|
||||||
|
|
@ -205,6 +210,7 @@ local function read_trash_info(info_file, cb)
|
||||||
cb(".trashinfo file points to non-existant file")
|
cb(".trashinfo file points to non-existant file")
|
||||||
else
|
else
|
||||||
trash_info.stat = trash_stat
|
trash_info.stat = trash_stat
|
||||||
|
---@cast trash_info oil.TrashInfo
|
||||||
cb(nil, trash_info)
|
cb(nil, trash_info)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
@ -242,7 +248,7 @@ M.list = function(url, column_defs, cb)
|
||||||
end
|
end
|
||||||
|
|
||||||
local info_dir = fs.join(trash_dir, "info")
|
local info_dir = fs.join(trash_dir, "info")
|
||||||
---@diagnostic disable-next-line: param-type-mismatch
|
---@diagnostic disable-next-line: param-type-mismatch, discard-returns
|
||||||
uv.fs_opendir(info_dir, function(open_err, fd)
|
uv.fs_opendir(info_dir, function(open_err, fd)
|
||||||
if open_err then
|
if open_err then
|
||||||
if open_err:match("^ENOENT: no such file or directory") then
|
if open_err:match("^ENOENT: no such file or directory") then
|
||||||
|
|
@ -375,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
|
||||||
|
|
@ -411,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))
|
||||||
|
|
@ -441,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
|
||||||
|
|
@ -555,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))
|
||||||
|
|
@ -570,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)
|
||||||
|
|
@ -590,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)
|
||||||
|
|
@ -602,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")
|
||||||
|
|
@ -619,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)
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ M.list = function(url, column_defs, cb)
|
||||||
local _, path = util.parse_url(url)
|
local _, path = util.parse_url(url)
|
||||||
assert(path)
|
assert(path)
|
||||||
local trash_dir = get_trash_dir()
|
local trash_dir = get_trash_dir()
|
||||||
---@diagnostic disable-next-line: param-type-mismatch
|
---@diagnostic disable-next-line: param-type-mismatch, discard-returns
|
||||||
uv.fs_opendir(trash_dir, function(open_err, fd)
|
uv.fs_opendir(trash_dir, function(open_err, fd)
|
||||||
if open_err then
|
if open_err then
|
||||||
if open_err:match("^ENOENT: no such file or directory") then
|
if open_err:match("^ENOENT: no such file or directory") then
|
||||||
|
|
@ -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")
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,22 @@ function PowershellConnection.new(init_command)
|
||||||
|
|
||||||
self:_init(init_command)
|
self:_init(init_command)
|
||||||
|
|
||||||
|
---@type oil.PowershellConnection
|
||||||
return self
|
return self
|
||||||
end
|
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({
|
||||||
|
|
@ -56,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'")
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ ConvertTo-Json $data -Compress
|
||||||
---@type nil|oil.PowershellConnection
|
---@type nil|oil.PowershellConnection
|
||||||
local list_entries_powershell
|
local list_entries_powershell
|
||||||
|
|
||||||
---@param cb fun(err?: string, raw_entries: oil.WindowsRawEntry[]?)
|
---@param cb fun(err?: string, raw_entries?: oil.WindowsRawEntry[])
|
||||||
M.list_raw_entries = function(cb)
|
M.list_raw_entries = function(cb)
|
||||||
if not list_entries_powershell then
|
if not list_entries_powershell then
|
||||||
list_entries_powershell = Powershell.new(list_entries_init)
|
list_entries_powershell = Powershell.new(list_entries_init)
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,7 @@ end
|
||||||
---@return nil|oil.InternalEntry
|
---@return nil|oil.InternalEntry
|
||||||
M.get_entry_by_url = function(url)
|
M.get_entry_by_url = function(url)
|
||||||
local scheme, path = util.parse_url(url)
|
local scheme, path = util.parse_url(url)
|
||||||
|
assert(path)
|
||||||
local parent_url = scheme .. vim.fn.fnamemodify(path, ":h")
|
local parent_url = scheme .. vim.fn.fnamemodify(path, ":h")
|
||||||
local basename = vim.fn.fnamemodify(path, ":t")
|
local basename = vim.fn.fnamemodify(path, ":t")
|
||||||
return M.list_url(parent_url)[basename]
|
return M.list_url(parent_url)[basename]
|
||||||
|
|
@ -150,11 +151,13 @@ end
|
||||||
M.perform_action = function(action)
|
M.perform_action = function(action)
|
||||||
if action.type == "create" then
|
if action.type == "create" then
|
||||||
local scheme, path = util.parse_url(action.url)
|
local scheme, path = util.parse_url(action.url)
|
||||||
|
assert(path)
|
||||||
local parent_url = util.addslash(scheme .. vim.fn.fnamemodify(path, ":h"))
|
local parent_url = util.addslash(scheme .. vim.fn.fnamemodify(path, ":h"))
|
||||||
local name = vim.fn.fnamemodify(path, ":t")
|
local name = vim.fn.fnamemodify(path, ":t")
|
||||||
M.create_and_store_entry(parent_url, name, action.entry_type)
|
M.create_and_store_entry(parent_url, name, action.entry_type)
|
||||||
elseif action.type == "delete" then
|
elseif action.type == "delete" then
|
||||||
local scheme, path = util.parse_url(action.url)
|
local scheme, path = util.parse_url(action.url)
|
||||||
|
assert(path)
|
||||||
local parent_url = util.addslash(scheme .. vim.fn.fnamemodify(path, ":h"))
|
local parent_url = util.addslash(scheme .. vim.fn.fnamemodify(path, ":h"))
|
||||||
local name = vim.fn.fnamemodify(path, ":t")
|
local name = vim.fn.fnamemodify(path, ":t")
|
||||||
local entry = url_directory[parent_url][name]
|
local entry = url_directory[parent_url][name]
|
||||||
|
|
@ -163,11 +166,13 @@ M.perform_action = function(action)
|
||||||
parent_url_by_id[entry[FIELD_ID]] = nil
|
parent_url_by_id[entry[FIELD_ID]] = nil
|
||||||
elseif action.type == "move" then
|
elseif action.type == "move" then
|
||||||
local src_scheme, src_path = util.parse_url(action.src_url)
|
local src_scheme, src_path = util.parse_url(action.src_url)
|
||||||
|
assert(src_path)
|
||||||
local src_parent_url = util.addslash(src_scheme .. vim.fn.fnamemodify(src_path, ":h"))
|
local src_parent_url = util.addslash(src_scheme .. vim.fn.fnamemodify(src_path, ":h"))
|
||||||
local src_name = vim.fn.fnamemodify(src_path, ":t")
|
local src_name = vim.fn.fnamemodify(src_path, ":t")
|
||||||
local entry = url_directory[src_parent_url][src_name]
|
local entry = url_directory[src_parent_url][src_name]
|
||||||
|
|
||||||
local dest_scheme, dest_path = util.parse_url(action.dest_url)
|
local dest_scheme, dest_path = util.parse_url(action.dest_url)
|
||||||
|
assert(dest_path)
|
||||||
local dest_parent_url = util.addslash(dest_scheme .. vim.fn.fnamemodify(dest_path, ":h"))
|
local dest_parent_url = util.addslash(dest_scheme .. vim.fn.fnamemodify(dest_path, ":h"))
|
||||||
local dest_name = vim.fn.fnamemodify(dest_path, ":t")
|
local dest_name = vim.fn.fnamemodify(dest_path, ":t")
|
||||||
|
|
||||||
|
|
@ -185,12 +190,14 @@ M.perform_action = function(action)
|
||||||
util.update_moved_buffers(action.entry_type, action.src_url, action.dest_url)
|
util.update_moved_buffers(action.entry_type, action.src_url, action.dest_url)
|
||||||
elseif action.type == "copy" then
|
elseif action.type == "copy" then
|
||||||
local scheme, path = util.parse_url(action.dest_url)
|
local scheme, path = util.parse_url(action.dest_url)
|
||||||
|
assert(path)
|
||||||
local parent_url = util.addslash(scheme .. vim.fn.fnamemodify(path, ":h"))
|
local parent_url = util.addslash(scheme .. vim.fn.fnamemodify(path, ":h"))
|
||||||
local name = vim.fn.fnamemodify(path, ":t")
|
local name = vim.fn.fnamemodify(path, ":t")
|
||||||
M.create_and_store_entry(parent_url, name, action.entry_type)
|
M.create_and_store_entry(parent_url, name, action.entry_type)
|
||||||
elseif action.type == "change" then
|
elseif action.type == "change" then
|
||||||
-- Cache doesn't need to update
|
-- Cache doesn't need to update
|
||||||
else
|
else
|
||||||
|
---@diagnostic disable-next-line: undefined-field
|
||||||
error(string.format("Bad action type: '%s'", action.type))
|
error(string.format("Bad action type: '%s'", action.type))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
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
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
local config = require("oil.config")
|
local config = require("oil.config")
|
||||||
local constants = require("oil.constants")
|
local constants = require("oil.constants")
|
||||||
local util = require("oil.util")
|
local util = require("oil.util")
|
||||||
local has_devicons, devicons = pcall(require, "nvim-web-devicons")
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local FIELD_NAME = constants.FIELD_NAME
|
local FIELD_NAME = constants.FIELD_NAME
|
||||||
|
|
@ -10,16 +9,16 @@ local FIELD_META = constants.FIELD_META
|
||||||
|
|
||||||
local all_columns = {}
|
local all_columns = {}
|
||||||
|
|
||||||
---@alias oil.ColumnSpec string|table
|
---@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
|
||||||
|
|
@ -54,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
|
||||||
|
|
@ -110,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
|
||||||
|
|
@ -150,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
|
||||||
|
|
||||||
|
|
@ -202,34 +150,35 @@ M.perform_change_action = function(adapter, action, callback)
|
||||||
column.perform_action(action, callback)
|
column.perform_action(action, callback)
|
||||||
end
|
end
|
||||||
|
|
||||||
if has_devicons then
|
local icon_provider = util.get_icon_provider()
|
||||||
|
if icon_provider then
|
||||||
M.register("icon", {
|
M.register("icon", {
|
||||||
render = function(entry, conf)
|
render = function(entry, conf)
|
||||||
local type = entry[FIELD_TYPE]
|
local field_type = entry[FIELD_TYPE]
|
||||||
local name = entry[FIELD_NAME]
|
local name = entry[FIELD_NAME]
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
if type == "link" and meta then
|
if field_type == "link" and meta then
|
||||||
if meta.link then
|
if meta.link then
|
||||||
name = meta.link
|
name = meta.link
|
||||||
end
|
end
|
||||||
if meta.link_stat then
|
if meta.link_stat then
|
||||||
type = meta.link_stat.type
|
field_type = meta.link_stat.type
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
local icon, hl
|
if meta and meta.display_name then
|
||||||
if type == "directory" then
|
name = meta.display_name
|
||||||
icon = conf and conf.directory or ""
|
|
||||||
hl = "OilDirIcon"
|
|
||||||
else
|
|
||||||
if meta and meta.display_name then
|
|
||||||
name = meta.display_name
|
|
||||||
end
|
|
||||||
icon, hl = devicons.get_icon(name)
|
|
||||||
icon = icon or (conf and conf.default_file or "")
|
|
||||||
end
|
end
|
||||||
|
local icon, hl = icon_provider(field_type, name, conf)
|
||||||
if not conf or conf.add_padding ~= false then
|
if not conf or conf.add_padding ~= false then
|
||||||
icon = icon .. " "
|
icon = icon .. " "
|
||||||
end
|
end
|
||||||
|
if conf and conf.highlight then
|
||||||
|
if type(conf.highlight) == "function" then
|
||||||
|
hl = conf.highlight(icon)
|
||||||
|
else
|
||||||
|
hl = conf.highlight
|
||||||
|
end
|
||||||
|
end
|
||||||
return { icon, hl }
|
return { icon, hl }
|
||||||
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,11 +241,32 @@ 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)
|
||||||
if config.view_options.natural_order then
|
if
|
||||||
return entry[FIELD_NAME]:gsub("%d+", pad_number)
|
config.view_options.natural_order == false
|
||||||
|
or (config.view_options.natural_order == "fast" and num_entries > 5000)
|
||||||
|
then
|
||||||
|
if config.view_options.case_insensitive then
|
||||||
|
return function(entry)
|
||||||
|
return entry[FIELD_NAME]:lower()
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return function(entry)
|
||||||
|
return entry[FIELD_NAME]
|
||||||
|
end
|
||||||
|
end
|
||||||
else
|
else
|
||||||
return entry[FIELD_NAME]
|
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
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
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 still want to use netrw.
|
-- Set to false if you want some other plugin (e.g. netrw) to open when you edit directories.
|
||||||
default_file_explorer = true,
|
default_file_explorer = true,
|
||||||
-- Id is automatically added at the beginning, and name at the end
|
-- Id is automatically added at the beginning, and name at the end
|
||||||
-- See :help oil-columns
|
-- See :help oil-columns
|
||||||
|
|
@ -38,6 +38,8 @@ local default_config = {
|
||||||
-- Note that the cleanup process only starts when none of the oil buffers are currently displayed
|
-- Note that the cleanup process only starts when none of the oil buffers are currently displayed
|
||||||
cleanup_delay_ms = 2000,
|
cleanup_delay_ms = 2000,
|
||||||
lsp_file_methods = {
|
lsp_file_methods = {
|
||||||
|
-- Enable or disable LSP file operations
|
||||||
|
enabled = true,
|
||||||
-- Time to wait for LSP file operations to complete before skipping
|
-- Time to wait for LSP file operations to complete before skipping
|
||||||
timeout_ms = 1000,
|
timeout_ms = 1000,
|
||||||
-- Set to true to autosave buffers that are updated with LSP willRenameFiles
|
-- Set to true to autosave buffers that are updated with LSP willRenameFiles
|
||||||
|
|
@ -48,7 +50,7 @@ local default_config = {
|
||||||
-- Set to `false` to disable, or "name" to keep it on the file names
|
-- Set to `false` to disable, or "name" to keep it on the file names
|
||||||
constrain_cursor = "editable",
|
constrain_cursor = "editable",
|
||||||
-- Set to true to watch the filesystem for changes and reload oil
|
-- Set to true to watch the filesystem for changes and reload oil
|
||||||
experimental_watch_for_changes = false,
|
watch_for_changes = false,
|
||||||
-- Keymaps in oil buffer. Can be any value that `vim.keymap.set` accepts OR a table of keymap
|
-- Keymaps in oil buffer. Can be any value that `vim.keymap.set` accepts OR a table of keymap
|
||||||
-- options with a `callback` (e.g. { callback = function() ... end, desc = "", mode = "n" })
|
-- options with a `callback` (e.g. { callback = function() ... end, desc = "", mode = "n" })
|
||||||
-- Additionally, if it is a string that matches "actions.<name>",
|
-- Additionally, if it is a string that matches "actions.<name>",
|
||||||
|
|
@ -56,26 +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_vsplit",
|
["<C-s>"] = { "actions.select", opts = { vertical = true } },
|
||||||
["<C-h>"] = "actions.select_split",
|
["<C-h>"] = { "actions.select", opts = { horizontal = true } },
|
||||||
["<C-t>"] = "actions.select_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.tcd",
|
["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" },
|
||||||
},
|
|
||||||
-- Configuration for the floating keymaps help window
|
|
||||||
keymaps_help = {
|
|
||||||
border = "rounded",
|
|
||||||
},
|
},
|
||||||
-- 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,40 +82,82 @@ 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
|
||||||
|
case_insensitive = false,
|
||||||
sort = {
|
sort = {
|
||||||
-- sort order can be "asc" or "desc"
|
-- sort order can be "asc" or "desc"
|
||||||
-- see :help oil-columns to see which columns are sortable
|
-- see :help oil-columns to see which columns are sortable
|
||||||
{ "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_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
|
||||||
|
git = {
|
||||||
|
-- Return true to automatically git add/mv/rm files
|
||||||
|
add = function(path)
|
||||||
|
return false
|
||||||
|
end,
|
||||||
|
mv = function(src_path, dest_path)
|
||||||
|
return false
|
||||||
|
end,
|
||||||
|
rm = function(path)
|
||||||
|
return false
|
||||||
|
end,
|
||||||
},
|
},
|
||||||
-- Configuration for the floating window in oil.open_float
|
-- Configuration for the floating window in oil.open_float
|
||||||
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,
|
||||||
},
|
},
|
||||||
|
-- optionally override the oil buffers window title with custom function: fun(winid: integer): string
|
||||||
|
get_win_title = nil,
|
||||||
|
-- preview_split: Split direction: "auto", "left", "right", "above", "below".
|
||||||
|
preview_split = "auto",
|
||||||
-- This is the config that will be passed to nvim_open_win.
|
-- This is the config that will be passed to nvim_open_win.
|
||||||
-- Change values here to customize the layout
|
-- Change values here to customize the layout
|
||||||
override = function(conf)
|
override = function(conf)
|
||||||
return conf
|
return conf
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
-- Configuration for the actions floating preview window
|
-- Configuration for the file preview window
|
||||||
preview = {
|
preview_win = {
|
||||||
|
-- Whether the preview window is automatically updated when the cursor is moved
|
||||||
|
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
|
||||||
|
confirmation = {
|
||||||
-- Width dimensions can be integers or a float between 0 and 1 (e.g. 0.4 for 40%)
|
-- Width dimensions can be integers or a float between 0 and 1 (e.g. 0.4 for 40%)
|
||||||
-- min_width and max_width can be a single value or a list of mixed integer/float types.
|
-- min_width and max_width can be a single value or a list of mixed integer/float types.
|
||||||
-- max_width = {100, 0.8} means "the lesser of 100 columns or 80% of total"
|
-- max_width = {100, 0.8} means "the lesser of 100 columns or 80% of total"
|
||||||
|
|
@ -134,12 +174,10 @@ 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,
|
||||||
},
|
},
|
||||||
-- Whether the preview window is automatically updated when the cursor is moved
|
|
||||||
update_on_cursor_moved = true,
|
|
||||||
},
|
},
|
||||||
-- Configuration for the floating progress window
|
-- Configuration for the floating progress window
|
||||||
progress = {
|
progress = {
|
||||||
|
|
@ -149,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,
|
||||||
|
|
@ -157,26 +195,242 @@ 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
|
||||||
|
keymaps_help = {
|
||||||
|
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
|
||||||
|
---@field adapters table<string, string> Hidden from SetupOpts
|
||||||
|
---@field adapter_aliases table<string, string> Hidden from SetupOpts
|
||||||
|
---@field silence_scp_warning? boolean Undocumented option
|
||||||
|
---@field default_file_explorer boolean
|
||||||
|
---@field columns oil.ColumnSpec[]
|
||||||
|
---@field buf_options table<string, any>
|
||||||
|
---@field win_options table<string, any>
|
||||||
|
---@field delete_to_trash boolean
|
||||||
|
---@field skip_confirm_for_simple_edits boolean
|
||||||
|
---@field prompt_save_on_select_new_entry boolean
|
||||||
|
---@field cleanup_delay_ms integer
|
||||||
|
---@field lsp_file_methods oil.LspFileMethods
|
||||||
|
---@field constrain_cursor false|"name"|"editable"
|
||||||
|
---@field watch_for_changes boolean
|
||||||
|
---@field keymaps table<string, any>
|
||||||
|
---@field use_default_keymaps boolean
|
||||||
|
---@field view_options oil.ViewOptions
|
||||||
|
---@field extra_scp_args string[]
|
||||||
|
---@field extra_s3_args string[]
|
||||||
|
---@field git oil.GitOptions
|
||||||
|
---@field float oil.FloatWindowConfig
|
||||||
|
---@field preview_win oil.PreviewWindowConfig
|
||||||
|
---@field confirmation oil.ConfirmationWindowConfig
|
||||||
|
---@field progress oil.ProgressWindowConfig
|
||||||
|
---@field ssh oil.SimpleWindowConfig
|
||||||
|
---@field keymaps_help oil.SimpleWindowConfig
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
|
-- For backwards compatibility
|
||||||
|
---@alias oil.setupOpts oil.SetupOpts
|
||||||
|
|
||||||
|
---@class (exact) oil.SetupOpts
|
||||||
|
---@field default_file_explorer? boolean Oil will take over directory buffers (e.g. `vim .` or `:e src/`). Set to false if you still want to use netrw.
|
||||||
|
---@field columns? oil.ColumnSpec[] The columns to display. See :help oil-columns.
|
||||||
|
---@field buf_options? table<string, any> Buffer-local options to use for oil buffers
|
||||||
|
---@field win_options? table<string, any> Window-local options to use for oil buffers
|
||||||
|
---@field delete_to_trash? boolean Send deleted files to the trash instead of permanently deleting them (:help oil-trash).
|
||||||
|
---@field skip_confirm_for_simple_edits? boolean Skip the confirmation popup for simple operations (:help oil.skip_confirm_for_simple_edits).
|
||||||
|
---@field prompt_save_on_select_new_entry? boolean Selecting a new/moved/renamed file or directory will prompt you to save changes first (:help prompt_save_on_select_new_entry).
|
||||||
|
---@field cleanup_delay_ms? integer Oil will automatically delete hidden buffers after this delay. You can set the delay to false to disable cleanup entirely. Note that the cleanup process only starts when none of the oil buffers are currently displayed.
|
||||||
|
---@field lsp_file_methods? oil.SetupLspFileMethods Configure LSP file operation integration.
|
||||||
|
---@field constrain_cursor? false|"name"|"editable" Constrain the cursor to the editable parts of the oil buffer. Set to `false` to disable, or "name" to keep it on the file names.
|
||||||
|
---@field watch_for_changes? boolean Set to true to watch the filesystem for changes and reload oil.
|
||||||
|
---@field keymaps? table<string, any>
|
||||||
|
---@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 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 float? oil.SetupFloatWindowConfig Configuration for the floating window in oil.open_float
|
||||||
|
---@field preview_win? oil.SetupPreviewWindowConfig Configuration for the file preview window
|
||||||
|
---@field confirmation? oil.SetupConfirmationWindowConfig Configuration for the floating action confirmation window
|
||||||
|
---@field progress? oil.SetupProgressWindowConfig Configuration for the floating progress window
|
||||||
|
---@field ssh? oil.SetupSimpleWindowConfig Configuration for the floating SSH window
|
||||||
|
---@field keymaps_help? oil.SetupSimpleWindowConfig Configuration for the floating keymaps help window
|
||||||
|
|
||||||
|
---@class (exact) oil.LspFileMethods
|
||||||
|
---@field enabled boolean
|
||||||
|
---@field timeout_ms integer
|
||||||
|
---@field autosave_changes boolean|"unmodified" Set to true to autosave buffers that are updated with LSP willRenameFiles. Set to "unmodified" to only save unmodified buffers.
|
||||||
|
|
||||||
|
---@class (exact) oil.SetupLspFileMethods
|
||||||
|
---@field enabled? boolean Enable or disable LSP file operations
|
||||||
|
---@field timeout_ms? integer Time to wait for LSP file operations to complete before skipping.
|
||||||
|
---@field autosave_changes? boolean|"unmodified" Set to true to autosave buffers that are updated with LSP willRenameFiles. Set to "unmodified" to only save unmodified buffers.
|
||||||
|
|
||||||
|
---@class (exact) oil.ViewOptions
|
||||||
|
---@field show_hidden boolean
|
||||||
|
---@field is_hidden_file fun(name: string, bufnr: integer): boolean
|
||||||
|
---@field is_always_hidden fun(name: string, bufnr: integer): boolean
|
||||||
|
---@field natural_order boolean|"fast"
|
||||||
|
---@field case_insensitive boolean
|
||||||
|
---@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
|
||||||
|
---@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_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|"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 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
|
||||||
|
---@field [1] string
|
||||||
|
---@field [2] "asc"|"desc"
|
||||||
|
|
||||||
|
---@class (exact) oil.GitOptions
|
||||||
|
---@field add fun(path: string): boolean
|
||||||
|
---@field mv fun(src_path: string, dest_path: string): boolean
|
||||||
|
---@field rm fun(path: string): boolean
|
||||||
|
|
||||||
|
---@class (exact) oil.SetupGitOptions
|
||||||
|
---@field add? fun(path: string): boolean Return true to automatically git add a new file
|
||||||
|
---@field mv? fun(src_path: string, dest_path: string): boolean Return true to automatically git mv a moved file
|
||||||
|
---@field rm? fun(path: string): boolean Return true to automatically git rm a deleted file
|
||||||
|
|
||||||
|
---@class (exact) oil.WindowDimensionDualConstraint
|
||||||
|
---@field [1] number
|
||||||
|
---@field [2] number
|
||||||
|
|
||||||
|
---@alias oil.WindowDimension number|oil.WindowDimensionDualConstraint
|
||||||
|
|
||||||
|
---@class (exact) oil.WindowConfig
|
||||||
|
---@field max_width oil.WindowDimension
|
||||||
|
---@field min_width oil.WindowDimension
|
||||||
|
---@field width? number
|
||||||
|
---@field max_height oil.WindowDimension
|
||||||
|
---@field min_height oil.WindowDimension
|
||||||
|
---@field height? number
|
||||||
|
---@field border string|string[]
|
||||||
|
---@field win_options table<string, any>
|
||||||
|
|
||||||
|
---@class (exact) oil.SetupWindowConfig
|
||||||
|
---@field max_width? oil.WindowDimension Width dimensions can be integers or a float between 0 and 1 (e.g. 0.4 for 40%). Can be a single value or a list of mixed integer/float types. max_width = {100, 0.8} means "the lesser of 100 columns or 80% of total"
|
||||||
|
---@field min_width? oil.WindowDimension Width dimensions can be integers or a float between 0 and 1 (e.g. 0.4 for 40%). Can be a single value or a list of mixed integer/float types. min_width = {40, 0.4} means "the greater of 40 columns or 40% of total"
|
||||||
|
---@field width? number Define an integer/float for the exact width of the preview window
|
||||||
|
---@field max_height? oil.WindowDimension Height dimensions can be integers or a float between 0 and 1 (e.g. 0.4 for 40%). Can be a single value or a list of mixed integer/float types. max_height = {80, 0.9} means "the lesser of 80 columns or 90% of total"
|
||||||
|
---@field min_height? oil.WindowDimension Height dimensions can be integers or a float between 0 and 1 (e.g. 0.4 for 40%). Can be a single value or a list of mixed integer/float types. min_height = {5, 0.1} means "the greater of 5 columns or 10% of total"
|
||||||
|
---@field height? number Define an integer/float for the exact height of the preview window
|
||||||
|
---@field border? string|string[] Window border
|
||||||
|
---@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
|
||||||
|
---@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.SetupPreviewWindowConfig
|
||||||
|
---@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.ProgressWindowConfig : oil.WindowConfig
|
||||||
|
---@field minimized_border string|string[]
|
||||||
|
|
||||||
|
---@class (exact) oil.SetupProgressWindowConfig : oil.SetupWindowConfig
|
||||||
|
---@field minimized_border? string|string[] The border for the minimized progress window
|
||||||
|
|
||||||
|
---@class (exact) oil.FloatWindowConfig
|
||||||
|
---@field padding integer
|
||||||
|
---@field max_width integer
|
||||||
|
---@field max_height integer
|
||||||
|
---@field border string|string[]
|
||||||
|
---@field win_options table<string, any>
|
||||||
|
---@field get_win_title fun(winid: integer): string
|
||||||
|
---@field preview_split "auto"|"left"|"right"|"above"|"below"
|
||||||
|
---@field override fun(conf: table): table
|
||||||
|
|
||||||
|
---@class (exact) oil.SetupFloatWindowConfig
|
||||||
|
---@field padding? integer
|
||||||
|
---@field max_width? integer
|
||||||
|
---@field max_height? integer
|
||||||
|
---@field border? string|string[] Window border
|
||||||
|
---@field win_options? table<string, any>
|
||||||
|
---@field get_win_title? fun(winid: integer): string
|
||||||
|
---@field preview_split? "auto"|"left"|"right"|"above"|"below" Direction that the preview command will split the window
|
||||||
|
---@field override? fun(conf: table): table
|
||||||
|
|
||||||
|
---@class (exact) oil.SimpleWindowConfig
|
||||||
|
---@field border string|string[]
|
||||||
|
|
||||||
|
---@class (exact) oil.SetupSimpleWindowConfig
|
||||||
|
---@field border? string|string[] Window border
|
||||||
|
|
||||||
M.setup = function(opts)
|
M.setup = function(opts)
|
||||||
local new_conf = vim.tbl_deep_extend("keep", opts or {}, default_config)
|
opts = opts or {}
|
||||||
|
|
||||||
|
local new_conf = vim.tbl_deep_extend("keep", opts, default_config)
|
||||||
if not new_conf.use_default_keymaps then
|
if not new_conf.use_default_keymaps then
|
||||||
new_conf.keymaps = opts.keymaps or {}
|
new_conf.keymaps = opts.keymaps or {}
|
||||||
|
elseif opts.keymaps then
|
||||||
|
-- We don't want to deep merge the keymaps, we want any keymap defined by the user to override
|
||||||
|
-- everything about the default.
|
||||||
|
for k, v in pairs(opts.keymaps) do
|
||||||
|
new_conf.keymaps[k] = v
|
||||||
|
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'.
|
||||||
|
if opts.preview and not opts.confirmation then
|
||||||
|
new_conf.confirmation = vim.tbl_deep_extend("keep", opts.preview, default_config.confirmation)
|
||||||
|
end
|
||||||
|
-- Backwards compatibility. We renamed the 'preview' config to 'preview_win'
|
||||||
|
if opts.preview and opts.preview.update_on_cursor_moved ~= nil then
|
||||||
|
new_conf.preview_win.update_on_cursor_moved = opts.preview.update_on_cursor_moved
|
||||||
end
|
end
|
||||||
|
|
||||||
if new_conf.lsp_rename_autosave ~= nil then
|
if new_conf.lsp_rename_autosave ~= nil then
|
||||||
|
|
@ -188,6 +442,11 @@ M.setup = function(opts)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- This option was renamed because it is no longer experimental
|
||||||
|
if new_conf.experimental_watch_for_changes then
|
||||||
|
new_conf.watch_for_changes = true
|
||||||
|
end
|
||||||
|
|
||||||
for k, v in pairs(new_conf) do
|
for k, v in pairs(new_conf) do
|
||||||
M[k] = v
|
M[k] = v
|
||||||
end
|
end
|
||||||
|
|
@ -217,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
|
||||||
|
|
@ -231,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
|
||||||
|
|
@ -163,7 +164,7 @@ end
|
||||||
---@param dir string
|
---@param dir string
|
||||||
---@param cb fun(err: nil|string, entries: nil|{type: oil.EntryType, name: string})
|
---@param cb fun(err: nil|string, entries: nil|{type: oil.EntryType, name: string})
|
||||||
M.listdir = function(dir, cb)
|
M.listdir = function(dir, cb)
|
||||||
---@diagnostic disable-next-line: param-type-mismatch
|
---@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)
|
||||||
if open_err then
|
if open_err then
|
||||||
return cb(open_err)
|
return cb(open_err)
|
||||||
|
|
@ -203,7 +204,7 @@ M.recursive_delete = function(entry_type, path, cb)
|
||||||
if entry_type ~= "directory" then
|
if entry_type ~= "directory" then
|
||||||
return uv.fs_unlink(path, cb)
|
return uv.fs_unlink(path, cb)
|
||||||
end
|
end
|
||||||
---@diagnostic disable-next-line: param-type-mismatch
|
---@diagnostic disable-next-line: param-type-mismatch, discard-returns
|
||||||
uv.fs_opendir(path, function(open_err, fd)
|
uv.fs_opendir(path, function(open_err, fd)
|
||||||
if open_err then
|
if open_err then
|
||||||
return cb(open_err)
|
return cb(open_err)
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -273,7 +306,7 @@ M.recursive_copy = function(entry_type, src_path, dest_path, cb)
|
||||||
if mkdir_err then
|
if mkdir_err then
|
||||||
return cb(mkdir_err)
|
return cb(mkdir_err)
|
||||||
end
|
end
|
||||||
---@diagnostic disable-next-line: param-type-mismatch
|
---@diagnostic disable-next-line: param-type-mismatch, discard-returns
|
||||||
uv.fs_opendir(src_path, function(open_err, fd)
|
uv.fs_opendir(src_path, function(open_err, fd)
|
||||||
if open_err then
|
if open_err then
|
||||||
return cb(open_err)
|
return cb(open_err)
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
118
lua/oil/git.lua
Normal file
118
lua/oil/git.lua
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
-- integration with git operations
|
||||||
|
local fs = require("oil.fs")
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
---@param path string
|
||||||
|
---@return string|nil
|
||||||
|
M.get_root = function(path)
|
||||||
|
local git_dir = vim.fs.find(".git", { upward = true, path = path })[1]
|
||||||
|
if git_dir then
|
||||||
|
return vim.fs.dirname(git_dir)
|
||||||
|
else
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param path string
|
||||||
|
---@param cb fun(err: nil|string)
|
||||||
|
M.add = function(path, cb)
|
||||||
|
local root = M.get_root(path)
|
||||||
|
if not root then
|
||||||
|
return cb()
|
||||||
|
end
|
||||||
|
|
||||||
|
local stderr = ""
|
||||||
|
local jid = vim.fn.jobstart({ "git", "add", path }, {
|
||||||
|
cwd = root,
|
||||||
|
stderr_buffered = true,
|
||||||
|
on_stderr = function(_, data)
|
||||||
|
stderr = table.concat(data, "\n")
|
||||||
|
end,
|
||||||
|
on_exit = function(_, code)
|
||||||
|
if code ~= 0 then
|
||||||
|
cb("Error in git add: " .. stderr)
|
||||||
|
else
|
||||||
|
cb()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
if jid <= 0 then
|
||||||
|
cb()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param path string
|
||||||
|
---@param cb fun(err: nil|string)
|
||||||
|
M.rm = function(path, cb)
|
||||||
|
local root = M.get_root(path)
|
||||||
|
if not root then
|
||||||
|
return cb()
|
||||||
|
end
|
||||||
|
|
||||||
|
local stderr = ""
|
||||||
|
local jid = vim.fn.jobstart({ "git", "rm", "-r", path }, {
|
||||||
|
cwd = root,
|
||||||
|
stderr_buffered = true,
|
||||||
|
on_stderr = function(_, data)
|
||||||
|
stderr = table.concat(data, "\n")
|
||||||
|
end,
|
||||||
|
on_exit = function(_, code)
|
||||||
|
if code ~= 0 then
|
||||||
|
stderr = vim.trim(stderr)
|
||||||
|
if stderr:match("^fatal: pathspec '.*' did not match any files$") then
|
||||||
|
cb()
|
||||||
|
else
|
||||||
|
cb("Error in git rm: " .. stderr)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
cb()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
if jid <= 0 then
|
||||||
|
cb()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param entry_type oil.EntryType
|
||||||
|
---@param src_path string
|
||||||
|
---@param dest_path string
|
||||||
|
---@param cb fun(err: nil|string)
|
||||||
|
M.mv = function(entry_type, src_path, dest_path, cb)
|
||||||
|
local src_git = M.get_root(src_path)
|
||||||
|
if not src_git or src_git ~= M.get_root(dest_path) then
|
||||||
|
fs.recursive_move(entry_type, src_path, dest_path, cb)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local stderr = ""
|
||||||
|
local jid = vim.fn.jobstart({ "git", "mv", src_path, dest_path }, {
|
||||||
|
cwd = src_git,
|
||||||
|
stderr_buffered = true,
|
||||||
|
on_stderr = function(_, data)
|
||||||
|
stderr = table.concat(data, "\n")
|
||||||
|
end,
|
||||||
|
on_exit = function(_, code)
|
||||||
|
if code ~= 0 then
|
||||||
|
stderr = vim.trim(stderr)
|
||||||
|
if
|
||||||
|
stderr:match("^fatal: not under version control")
|
||||||
|
or stderr:match("^fatal: source directory is empty")
|
||||||
|
then
|
||||||
|
fs.recursive_move(entry_type, src_path, dest_path, cb)
|
||||||
|
else
|
||||||
|
cb("Error in git mv: " .. stderr)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
cb()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
if jid <= 0 then
|
||||||
|
-- Failed to run git, fall back to normal filesystem operations
|
||||||
|
fs.recursive_move(entry_type, src_path, dest_path, cb)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
642
lua/oil/init.lua
642
lua/oil/init.lua
|
|
@ -5,8 +5,9 @@ local M = {}
|
||||||
---@field type oil.EntryType
|
---@field type oil.EntryType
|
||||||
---@field id nil|integer Will be nil if it hasn't been persisted to disk yet
|
---@field id nil|integer Will be nil if it hasn't been persisted to disk yet
|
||||||
---@field parsed_name nil|string
|
---@field parsed_name nil|string
|
||||||
|
---@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
|
||||||
|
|
@ -18,6 +19,7 @@ local M = {}
|
||||||
---@field list fun(path: string, column_defs: string[], cb: fun(err?: string, entries?: oil.InternalEntry[], fetch_more?: fun())) Async function to list a directory.
|
---@field list fun(path: string, column_defs: string[], cb: fun(err?: string, entries?: oil.InternalEntry[], fetch_more?: fun())) Async function to list a directory.
|
||||||
---@field is_modifiable fun(bufnr: integer): boolean Return true if this directory is modifiable (allows for directories with read-only permissions).
|
---@field is_modifiable fun(bufnr: integer): boolean Return true if this directory is modifiable (allows for directories with read-only permissions).
|
||||||
---@field get_column fun(name: string): nil|oil.ColumnDefinition If the adapter has any adapter-specific columns, return them when fetched by name.
|
---@field get_column fun(name: string): nil|oil.ColumnDefinition If the adapter has any adapter-specific columns, return them when fetched by name.
|
||||||
|
---@field get_parent? fun(bufname: string): string Get the parent url of the given buffer
|
||||||
---@field normalize_url fun(url: string, callback: fun(url: string)) Before oil opens a url it will be normalized. This allows for link following, path normalizing, and converting an oil file url to the actual path of a file.
|
---@field normalize_url fun(url: string, callback: fun(url: string)) Before oil opens a url it will be normalized. This allows for link following, path normalizing, and converting an oil file url to the actual path of a file.
|
||||||
---@field get_entry_path? fun(url: string, entry: oil.Entry, callback: fun(path: string)) Similar to normalize_url, but used when selecting an entry
|
---@field get_entry_path? fun(url: string, entry: oil.Entry, callback: fun(path: string)) Similar to normalize_url, but used when selecting an entry
|
||||||
---@field render_action? fun(action: oil.Action): string Render a mutation action for display in the preview window. Only needed if adapter is modifiable.
|
---@field render_action? fun(action: oil.Action): string Render a mutation action for display in the preview window. Only needed if adapter is modifiable.
|
||||||
|
|
@ -28,10 +30,6 @@ local M = {}
|
||||||
---@field filter_action? fun(action: oil.Action): boolean When present, filter out actions as they are created
|
---@field filter_action? fun(action: oil.Action): boolean When present, filter out actions as they are created
|
||||||
---@field filter_error? fun(action: oil.ParseError): boolean When present, filter out errors from parsing a buffer
|
---@field filter_error? fun(action: oil.ParseError): boolean When present, filter out errors from parsing a buffer
|
||||||
|
|
||||||
-- TODO remove after https://github.com/folke/neodev.nvim/pull/163 lands
|
|
||||||
---@diagnostic disable: undefined-field
|
|
||||||
---@diagnostic disable: inject-field
|
|
||||||
|
|
||||||
---Get the entry on a specific line (1-indexed)
|
---Get the entry on a specific line (1-indexed)
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
---@param lnum integer
|
---@param lnum integer
|
||||||
|
|
@ -123,13 +121,15 @@ M.set_columns = function(cols)
|
||||||
end
|
end
|
||||||
|
|
||||||
---Change the sort order for oil
|
---Change the sort order for oil
|
||||||
---@param sort string[][]
|
---@param sort oil.SortSpec[] List of columns plus direction. See :help oil-columns to see which ones are sortable.
|
||||||
|
---@example
|
||||||
|
--- require("oil").set_sort({ { "type", "asc" }, { "size", "desc" } })
|
||||||
M.set_sort = function(sort)
|
M.set_sort = function(sort)
|
||||||
require("oil.view").set_sort(sort)
|
require("oil.view").set_sort(sort)
|
||||||
end
|
end
|
||||||
|
|
||||||
---Change how oil determines if the file is hidden
|
---Change how oil determines if the file is hidden
|
||||||
---@param is_hidden_file fun(filename: string, bufnr: nil|integer): boolean Return true if the file/dir should be hidden
|
---@param is_hidden_file fun(filename: string, bufnr: integer): boolean Return true if the file/dir should be hidden
|
||||||
M.set_is_hidden_file = function(is_hidden_file)
|
M.set_is_hidden_file = function(is_hidden_file)
|
||||||
require("oil.view").set_is_hidden_file(is_hidden_file)
|
require("oil.view").set_is_hidden_file(is_hidden_file)
|
||||||
end
|
end
|
||||||
|
|
@ -140,12 +140,14 @@ M.toggle_hidden = function()
|
||||||
end
|
end
|
||||||
|
|
||||||
---Get the current directory
|
---Get the current directory
|
||||||
|
---@param bufnr? integer
|
||||||
---@return nil|string
|
---@return nil|string
|
||||||
M.get_current_dir = function()
|
M.get_current_dir = function(bufnr)
|
||||||
local config = require("oil.config")
|
local config = require("oil.config")
|
||||||
local fs = require("oil.fs")
|
local fs = require("oil.fs")
|
||||||
local util = require("oil.util")
|
local util = require("oil.util")
|
||||||
local scheme, path = util.parse_url(vim.api.nvim_buf_get_name(0))
|
local buf_name = vim.api.nvim_buf_get_name(bufnr or 0)
|
||||||
|
local scheme, path = util.parse_url(buf_name)
|
||||||
if config.adapters[scheme] == "files" then
|
if config.adapters[scheme] == "files" then
|
||||||
assert(path)
|
assert(path)
|
||||||
return fs.posix_to_os_path(path)
|
return fs.posix_to_os_path(path)
|
||||||
|
|
@ -220,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]
|
||||||
|
|
@ -237,48 +239,28 @@ 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
|
||||||
|
|
||||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||||
vim.bo[bufnr].bufhidden = "wipe"
|
vim.bo[bufnr].bufhidden = "wipe"
|
||||||
local total_width = vim.o.columns
|
local win_opts = layout.get_fullscreen_win_opts()
|
||||||
local total_height = layout.get_editor_height()
|
|
||||||
local width = total_width - 2 * config.float.padding
|
|
||||||
if config.float.border ~= "none" then
|
|
||||||
width = width - 2 -- The border consumes 1 col on each side
|
|
||||||
end
|
|
||||||
if config.float.max_width > 0 then
|
|
||||||
width = math.min(width, config.float.max_width)
|
|
||||||
end
|
|
||||||
local height = total_height - 2 * config.float.padding
|
|
||||||
if config.float.max_height > 0 then
|
|
||||||
height = math.min(height, config.float.max_height)
|
|
||||||
end
|
|
||||||
local row = math.floor((total_height - height) / 2)
|
|
||||||
local col = math.floor((total_width - width) / 2) - 1 -- adjust for border width
|
|
||||||
local win_opts = {
|
|
||||||
relative = "editor",
|
|
||||||
width = width,
|
|
||||||
height = height,
|
|
||||||
row = row,
|
|
||||||
col = col,
|
|
||||||
border = config.float.border,
|
|
||||||
zindex = 45,
|
|
||||||
}
|
|
||||||
win_opts = config.float.override(win_opts) or win_opts
|
|
||||||
|
|
||||||
local original_winid = vim.api.nvim_get_current_win()
|
local original_winid = vim.api.nvim_get_current_win()
|
||||||
local winid = vim.api.nvim_open_win(bufnr, true, win_opts)
|
local winid = vim.api.nvim_open_win(bufnr, true, win_opts)
|
||||||
|
|
@ -294,7 +276,7 @@ M.open_float = function(dir)
|
||||||
desc = "Close floating oil window",
|
desc = "Close floating oil window",
|
||||||
group = "Oil",
|
group = "Oil",
|
||||||
callback = vim.schedule_wrap(function()
|
callback = vim.schedule_wrap(function()
|
||||||
if util.is_floating_win() then
|
if util.is_floating_win() or vim.fn.win_gettype() == "command" then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
if vim.api.nvim_win_is_valid(winid) then
|
if vim.api.nvim_win_is_valid(winid) then
|
||||||
|
|
@ -309,48 +291,50 @@ M.open_float = function(dir)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
-- Update the window title when we switch buffers
|
table.insert(
|
||||||
if vim.fn.has("nvim-0.9") == 1 and config.float.border ~= "none" then
|
autocmds,
|
||||||
local function get_title()
|
vim.api.nvim_create_autocmd("BufWinEnter", {
|
||||||
local src_buf = vim.api.nvim_win_get_buf(winid)
|
desc = "Reset local oil window options when buffer changes",
|
||||||
local title = vim.api.nvim_buf_get_name(src_buf)
|
pattern = "*",
|
||||||
local scheme, path = util.parse_url(title)
|
callback = function(params)
|
||||||
if config.adapters[scheme] == "files" then
|
local winbuf = params.buf
|
||||||
assert(path)
|
if not vim.api.nvim_win_is_valid(winid) or vim.api.nvim_win_get_buf(winid) ~= winbuf then
|
||||||
local fs = require("oil.fs")
|
return
|
||||||
title = vim.fn.fnamemodify(fs.posix_to_os_path(path), ":~")
|
end
|
||||||
end
|
for k, v in pairs(config.float.win_options) do
|
||||||
return title
|
vim.api.nvim_set_option_value(k, v, { scope = "local", win = winid })
|
||||||
end
|
end
|
||||||
table.insert(
|
|
||||||
autocmds,
|
-- Update the floating window title
|
||||||
vim.api.nvim_create_autocmd("BufWinEnter", {
|
if vim.fn.has("nvim-0.9") == 1 and config.float.border ~= "none" then
|
||||||
desc = "Update oil floating window title when buffer changes",
|
local cur_win_opts = vim.api.nvim_win_get_config(winid)
|
||||||
pattern = "*",
|
|
||||||
callback = function(params)
|
|
||||||
local winbuf = params.buf
|
|
||||||
if not vim.api.nvim_win_is_valid(winid) or vim.api.nvim_win_get_buf(winid) ~= winbuf then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
vim.api.nvim_win_set_config(winid, {
|
vim.api.nvim_win_set_config(winid, {
|
||||||
relative = "editor",
|
relative = "editor",
|
||||||
row = win_opts.row,
|
row = cur_win_opts.row,
|
||||||
col = win_opts.col,
|
col = cur_win_opts.col,
|
||||||
width = win_opts.width,
|
width = cur_win_opts.width,
|
||||||
height = win_opts.height,
|
height = cur_win_opts.height,
|
||||||
title = get_title(),
|
title = util.get_title(winid),
|
||||||
})
|
})
|
||||||
end,
|
end
|
||||||
})
|
end,
|
||||||
)
|
})
|
||||||
end
|
)
|
||||||
|
|
||||||
vim.cmd.edit({ args = { util.escape_filename(parent_url) }, mods = { keepalt = true } })
|
vim.cmd.edit({ args = { util.escape_filename(parent_url) }, mods = { keepalt = true } })
|
||||||
-- :edit will set buflisted = true, but we may not want that
|
-- :edit will set buflisted = true, but we may not want that
|
||||||
if config.buf_options.buflisted ~= nil then
|
if config.buf_options.buflisted ~= nil then
|
||||||
vim.api.nvim_buf_set_option(0, "buflisted", config.buf_options.buflisted)
|
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
|
||||||
|
|
@ -358,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
|
||||||
|
|
||||||
|
|
@ -372,42 +361,55 @@ local function update_preview_window(oil_bufnr)
|
||||||
local util = require("oil.util")
|
local util = require("oil.util")
|
||||||
util.run_after_load(oil_bufnr, function()
|
util.run_after_load(oil_bufnr, function()
|
||||||
local cursor_entry = M.get_cursor_entry()
|
local cursor_entry = M.get_cursor_entry()
|
||||||
if cursor_entry then
|
local preview_win_id = util.get_preview_win()
|
||||||
local preview_win_id = util.get_preview_win()
|
if
|
||||||
if preview_win_id then
|
cursor_entry
|
||||||
if cursor_entry.id ~= vim.w[preview_win_id].oil_entry_id then
|
and preview_win_id
|
||||||
M.select({ preview = true })
|
and cursor_entry.id ~= vim.w[preview_win_id].oil_entry_id
|
||||||
end
|
then
|
||||||
end
|
M.open_preview()
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
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
|
||||||
vim.cmd.edit({ args = { util.escape_filename(parent_url) }, mods = { keepalt = true } })
|
vim.cmd.edit({ args = { util.escape_filename(parent_url) }, mods = { keepalt = true } })
|
||||||
-- :edit will set buflisted = true, but we may not want that
|
-- :edit will set buflisted = true, but we may not want that
|
||||||
if config.buf_options.buflisted ~= nil then
|
if config.buf_options.buflisted ~= nil then
|
||||||
vim.api.nvim_buf_set_option(0, "buflisted", config.buf_options.buflisted)
|
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
|
||||||
|
|
@ -430,29 +432,32 @@ 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
|
||||||
vim.cmd.enew()
|
if opts.exit_if_last_buf then
|
||||||
|
vim.cmd.quit()
|
||||||
|
else
|
||||||
|
vim.cmd.enew()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
vim.api.nvim_buf_delete(oilbuf, { force = true })
|
vim.api.nvim_buf_delete(oilbuf, { force = true })
|
||||||
end
|
end
|
||||||
|
|
||||||
---Select the entry under the cursor
|
---@class oil.OpenPreviewOpts
|
||||||
---@param opts nil|table
|
---@field vertical? boolean Open the buffer in a vertical split
|
||||||
--- vertical boolean Open the buffer in a vertical split
|
---@field horizontal? boolean Open the buffer in a horizontal split
|
||||||
--- horizontal boolean Open the buffer in a horizontal split
|
---@field split? "aboveleft"|"belowright"|"topleft"|"botright" Split modifier
|
||||||
--- split "aboveleft"|"belowright"|"topleft"|"botright" Split modifier
|
|
||||||
--- preview boolean Open the buffer in a preview window
|
---Preview the entry under the cursor in a split
|
||||||
--- tab boolean Open the buffer in a new tab
|
---@param opts? oil.OpenPreviewOpts
|
||||||
--- close boolean Close the original oil buffer once selection is made
|
---@param callback? fun(err: nil|string) Called once the preview window has been opened
|
||||||
---@param callback nil|fun(err: nil|string) Called once all entries have been opened
|
M.open_preview = function(opts, callback)
|
||||||
M.select = function(opts, callback)
|
opts = opts or {}
|
||||||
local cache = require("oil.cache")
|
|
||||||
local config = require("oil.config")
|
local config = require("oil.config")
|
||||||
local constants = require("oil.constants")
|
local layout = require("oil.layout")
|
||||||
local pathutil = require("oil.pathutil")
|
local util = require("oil.util")
|
||||||
local FIELD_META = constants.FIELD_META
|
|
||||||
opts = vim.tbl_extend("keep", opts or {}, {})
|
|
||||||
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)
|
||||||
|
|
@ -461,29 +466,202 @@ M.select = function(opts, callback)
|
||||||
callback(err)
|
callback(err)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if opts.preview and not opts.horizontal and opts.vertical == nil then
|
|
||||||
|
if not opts.horizontal and opts.vertical == nil then
|
||||||
opts.vertical = true
|
opts.vertical = true
|
||||||
end
|
end
|
||||||
if not opts.split and (opts.horizontal or opts.vertical or opts.preview) then
|
if not opts.split then
|
||||||
if opts.horizontal then
|
if opts.horizontal then
|
||||||
opts.split = vim.o.splitbelow and "belowright" or "aboveleft"
|
opts.split = vim.o.splitbelow and "belowright" or "aboveleft"
|
||||||
else
|
else
|
||||||
opts.split = vim.o.splitright and "belowright" or "aboveleft"
|
opts.split = vim.o.splitright and "belowright" or "aboveleft"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if opts.tab and (opts.preview or opts.split) then
|
|
||||||
return finish("Cannot set preview or split when tab = true")
|
local preview_win = util.get_preview_win({ include_not_owned = true })
|
||||||
|
local prev_win = vim.api.nvim_get_current_win()
|
||||||
|
local bufnr = vim.api.nvim_get_current_buf()
|
||||||
|
|
||||||
|
local entry = M.get_cursor_entry()
|
||||||
|
if not entry then
|
||||||
|
return finish("Could not find entry under cursor")
|
||||||
end
|
end
|
||||||
if opts.close and opts.preview then
|
local entry_title = entry.name
|
||||||
return finish("Cannot use close=true with preview=true")
|
if entry.type == "directory" then
|
||||||
|
entry_title = entry_title .. "/"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if util.is_floating_win() then
|
||||||
|
if preview_win == nil then
|
||||||
|
local root_win_opts, preview_win_opts =
|
||||||
|
layout.split_window(0, config.float.preview_split, config.float.padding)
|
||||||
|
|
||||||
|
local win_opts_oil = {
|
||||||
|
relative = "editor",
|
||||||
|
width = root_win_opts.width,
|
||||||
|
height = root_win_opts.height,
|
||||||
|
row = root_win_opts.row,
|
||||||
|
col = root_win_opts.col,
|
||||||
|
border = config.float.border,
|
||||||
|
zindex = 45,
|
||||||
|
}
|
||||||
|
vim.api.nvim_win_set_config(0, win_opts_oil)
|
||||||
|
local win_opts = {
|
||||||
|
relative = "editor",
|
||||||
|
width = preview_win_opts.width,
|
||||||
|
height = preview_win_opts.height,
|
||||||
|
row = preview_win_opts.row,
|
||||||
|
col = preview_win_opts.col,
|
||||||
|
border = config.float.border,
|
||||||
|
zindex = 45,
|
||||||
|
focusable = false,
|
||||||
|
noautocmd = true,
|
||||||
|
style = "minimal",
|
||||||
|
}
|
||||||
|
|
||||||
|
if vim.fn.has("nvim-0.9") == 1 then
|
||||||
|
win_opts.title = entry_title
|
||||||
|
end
|
||||||
|
|
||||||
|
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_win_set_var(preview_win, "oil_preview", true)
|
||||||
|
vim.api.nvim_set_current_win(prev_win)
|
||||||
|
elseif vim.fn.has("nvim-0.9") == 1 then
|
||||||
|
vim.api.nvim_win_set_config(preview_win, { title = entry_title })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local cmd = preview_win and "buffer" or "sbuffer"
|
||||||
|
local mods = {
|
||||||
|
vertical = opts.vertical,
|
||||||
|
horizontal = opts.horizontal,
|
||||||
|
split = opts.split,
|
||||||
|
}
|
||||||
|
|
||||||
|
-- HACK Switching windows takes us out of visual mode.
|
||||||
|
-- Switching with nvim_set_current_win causes the previous visual selection (as used by `gv`) to
|
||||||
|
-- not get set properly. So we have to switch windows this way instead.
|
||||||
|
local hack_set_win = function(winid)
|
||||||
|
local winnr = vim.api.nvim_win_get_number(winid)
|
||||||
|
vim.cmd.wincmd({ args = { "w" }, count = winnr })
|
||||||
|
end
|
||||||
|
|
||||||
|
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()
|
||||||
|
if preview_win then
|
||||||
|
if is_visual_mode then
|
||||||
|
hack_set_win(preview_win)
|
||||||
|
else
|
||||||
|
vim.api.nvim_set_current_win(preview_win)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
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 not filebufnr then
|
||||||
|
filebufnr = vim.fn.bufadd(normalized_url)
|
||||||
|
if entry_is_file and vim.fn.bufloaded(filebufnr) == 0 then
|
||||||
|
vim.bo[filebufnr].bufhidden = "wipe"
|
||||||
|
vim.b[filebufnr].oil_preview_buffer = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: param-type-mismatch
|
||||||
|
local ok, err = pcall(vim.cmd, {
|
||||||
|
cmd = cmd,
|
||||||
|
args = { filebufnr },
|
||||||
|
mods = mods,
|
||||||
|
})
|
||||||
|
-- Ignore swapfile errors
|
||||||
|
if not ok and err and not err:match("^Vim:E325:") then
|
||||||
|
vim.api.nvim_echo({ { err, "Error" } }, true, {})
|
||||||
|
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_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_source_win = prev_win
|
||||||
|
if has_multicursors then
|
||||||
|
hack_set_win(prev_win)
|
||||||
|
mc.restoreCursors()
|
||||||
|
elseif is_visual_mode then
|
||||||
|
hack_set_win(prev_win)
|
||||||
|
-- Restore the visual selection
|
||||||
|
vim.cmd.normal({ args = { "gv" }, bang = true })
|
||||||
|
else
|
||||||
|
vim.api.nvim_set_current_win(prev_win)
|
||||||
|
end
|
||||||
|
finish()
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@class (exact) oil.SelectOpts
|
||||||
|
---@field vertical? boolean Open the buffer in a vertical split
|
||||||
|
---@field horizontal? boolean Open the buffer in a horizontal split
|
||||||
|
---@field split? "aboveleft"|"belowright"|"topleft"|"botright" Split modifier
|
||||||
|
---@field tab? boolean Open the buffer in a new tab
|
||||||
|
---@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
|
||||||
|
---@param opts nil|oil.SelectOpts
|
||||||
|
---@param callback nil|fun(err: nil|string) Called once all entries have been opened
|
||||||
|
M.select = function(opts, callback)
|
||||||
|
local cache = require("oil.cache")
|
||||||
|
local config = require("oil.config")
|
||||||
|
local constants = require("oil.constants")
|
||||||
local util = require("oil.util")
|
local util = require("oil.util")
|
||||||
if util.is_floating_win() and opts.preview then
|
local FIELD_META = constants.FIELD_META
|
||||||
return finish("oil preview doesn't work in a floating window")
|
opts = vim.tbl_extend("keep", opts or {}, {})
|
||||||
|
|
||||||
|
local function finish(err)
|
||||||
|
if err then
|
||||||
|
vim.notify(err, vim.log.levels.ERROR)
|
||||||
|
end
|
||||||
|
if callback then
|
||||||
|
callback(err)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not opts.split and (opts.horizontal or opts.vertical) then
|
||||||
|
if opts.horizontal then
|
||||||
|
opts.split = vim.o.splitbelow and "belowright" or "aboveleft"
|
||||||
|
else
|
||||||
|
opts.split = vim.o.splitright and "belowright" or "aboveleft"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if opts.tab and opts.split then
|
||||||
|
return finish("Cannot use split=true when tab = true")
|
||||||
end
|
end
|
||||||
local adapter = util.get_adapter(0)
|
local adapter = util.get_adapter(0)
|
||||||
if not adapter then
|
if not adapter then
|
||||||
return finish("Could not find adapter for current buffer")
|
return finish("Not an oil buffer")
|
||||||
end
|
end
|
||||||
|
|
||||||
local visual_range = util.get_visual_range()
|
local visual_range = util.get_visual_range()
|
||||||
|
|
@ -506,10 +684,6 @@ M.select = function(opts, callback)
|
||||||
if vim.tbl_isempty(entries) then
|
if vim.tbl_isempty(entries) then
|
||||||
return finish("Could not find entry under cursor")
|
return finish("Could not find entry under cursor")
|
||||||
end
|
end
|
||||||
if #entries > 1 and opts.preview then
|
|
||||||
vim.notify("Cannot preview multiple entries", vim.log.levels.WARN)
|
|
||||||
entries = { entries[1] }
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Check if any of these entries are moved from their original location
|
-- Check if any of these entries are moved from their original location
|
||||||
local bufname = vim.api.nvim_buf_get_name(0)
|
local bufname = vim.api.nvim_buf_get_name(0)
|
||||||
|
|
@ -533,7 +707,7 @@ M.select = function(opts, callback)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if any_moved and not opts.preview and config.prompt_save_on_select_new_entry then
|
if any_moved and config.prompt_save_on_select_new_entry then
|
||||||
local ok, choice = pcall(vim.fn.confirm, "Save changes?", "Yes\nNo", 1)
|
local ok, choice = pcall(vim.fn.confirm, "Save changes?", "Yes\nNo", 1)
|
||||||
if not ok then
|
if not ok then
|
||||||
return finish()
|
return finish()
|
||||||
|
|
@ -543,11 +717,9 @@ M.select = function(opts, callback)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local preview_win = util.get_preview_win()
|
|
||||||
local prev_win = vim.api.nvim_get_current_win()
|
local prev_win = vim.api.nvim_get_current_win()
|
||||||
|
local oil_bufnr = vim.api.nvim_get_current_buf()
|
||||||
|
|
||||||
local scheme, dir = util.parse_url(bufname)
|
|
||||||
assert(scheme and dir)
|
|
||||||
-- Async iter over entries so we can normalize the url before opening
|
-- Async iter over entries so we can normalize the url before opening
|
||||||
local i = 1
|
local i = 1
|
||||||
local function open_next_entry(cb)
|
local function open_next_entry(cb)
|
||||||
|
|
@ -556,16 +728,7 @@ M.select = function(opts, callback)
|
||||||
if not entry then
|
if not entry then
|
||||||
return cb()
|
return cb()
|
||||||
end
|
end
|
||||||
local url = scheme .. dir .. entry.name
|
if util.is_directory(entry) then
|
||||||
local is_directory = entry.type == "directory"
|
|
||||||
or (
|
|
||||||
entry.type == "link"
|
|
||||||
and entry.meta
|
|
||||||
and entry.meta.link_stat
|
|
||||||
and entry.meta.link_stat.type == "directory"
|
|
||||||
)
|
|
||||||
if is_directory then
|
|
||||||
url = url .. "/"
|
|
||||||
-- If this is a new directory BUT we think we already have an entry with this name, disallow
|
-- If this is a new directory BUT we think we already have an entry with this name, disallow
|
||||||
-- entry. This prevents the case of MOVE /foo -> /bar + CREATE /foo.
|
-- entry. This prevents the case of MOVE /foo -> /bar + CREATE /foo.
|
||||||
-- If you enter the new /foo, it will show the contents of the old /foo.
|
-- If you enter the new /foo, it will show the contents of the old /foo.
|
||||||
|
|
@ -573,82 +736,53 @@ M.select = function(opts, callback)
|
||||||
return cb("Please save changes before entering new directory")
|
return cb("Please save changes before entering new directory")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
-- 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
|
|
||||||
|
|
||||||
local get_edit_path
|
|
||||||
if entry.name == ".." then
|
|
||||||
get_edit_path = function(edit_cb)
|
|
||||||
edit_cb(scheme .. pathutil.parent(dir))
|
|
||||||
end
|
|
||||||
elseif adapter.get_entry_path then
|
|
||||||
get_edit_path = function(edit_cb)
|
|
||||||
adapter.get_entry_path(url, entry, edit_cb)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
get_edit_path = function(edit_cb)
|
|
||||||
adapter.normalize_url(url, edit_cb)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Normalize the url before opening to prevent needing to rename them inside the BufReadCmd
|
-- Normalize the url before opening to prevent needing to rename them inside the BufReadCmd
|
||||||
-- Renaming buffers during opening can lead to missed autocmds
|
-- Renaming buffers during opening can lead to missed autocmds
|
||||||
get_edit_path(function(normalized_url)
|
util.get_edit_path(oil_bufnr, entry, function(normalized_url)
|
||||||
local mods = {
|
local mods = {
|
||||||
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, "/")
|
||||||
|
|
||||||
if opts.preview then
|
-- The :buffer command doesn't set buflisted=true
|
||||||
-- If we're previewing a file that hasn't been opened yet, make sure it gets deleted after
|
-- So do that for normal files or for oil dirs if config set buflisted=true
|
||||||
-- we close the window
|
if entry_is_file or config.buf_options.buflisted then
|
||||||
if entry_is_file and vim.fn.bufloaded(filebufnr) == 0 then
|
|
||||||
vim.bo[filebufnr].bufhidden = "wipe"
|
|
||||||
vim.b[filebufnr].oil_preview_buffer = true
|
|
||||||
end
|
|
||||||
elseif entry_is_file then
|
|
||||||
-- The :buffer command doesn't set buflisted=true
|
|
||||||
-- So do that for non-diretory-buffers
|
|
||||||
vim.bo[filebufnr].buflisted = true
|
vim.bo[filebufnr].buflisted = true
|
||||||
end
|
end
|
||||||
|
|
||||||
local cmd
|
local cmd = "buffer"
|
||||||
if opts.preview and preview_win then
|
if opts.tab then
|
||||||
vim.api.nvim_set_current_win(preview_win)
|
vim.cmd.tabnew({ mods = mods })
|
||||||
cmd = "buffer"
|
-- Make sure the new buffer from tabnew gets cleaned up
|
||||||
|
vim.bo.bufhidden = "wipe"
|
||||||
|
elseif opts.split then
|
||||||
|
cmd = "sbuffer"
|
||||||
|
end
|
||||||
|
if opts.handle_buffer_callback ~= nil then
|
||||||
|
opts.handle_buffer_callback(filebufnr)
|
||||||
else
|
else
|
||||||
if opts.tab then
|
---@diagnostic disable-next-line: param-type-mismatch
|
||||||
vim.cmd.tabnew({ mods = mods })
|
local ok, err = pcall(vim.cmd, {
|
||||||
cmd = "buffer"
|
cmd = cmd,
|
||||||
elseif opts.split then
|
args = { filebufnr },
|
||||||
cmd = "sbuffer"
|
mods = mods,
|
||||||
else
|
})
|
||||||
cmd = "buffer"
|
-- Ignore swapfile errors
|
||||||
|
if not ok and err and not err:match("^Vim:E325:") then
|
||||||
|
vim.api.nvim_echo({ { err, "Error" } }, true, {})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
---@diagnostic disable-next-line: param-type-mismatch
|
|
||||||
local ok, err = pcall(vim.cmd, {
|
|
||||||
cmd = cmd,
|
|
||||||
args = { filebufnr },
|
|
||||||
mods = mods,
|
|
||||||
})
|
|
||||||
-- Ignore swapfile errors
|
|
||||||
if not ok and err and not err:match("^Vim:E325:") then
|
|
||||||
vim.api.nvim_echo({ { err, "Error" } }, true, {})
|
|
||||||
end
|
|
||||||
|
|
||||||
if opts.preview then
|
|
||||||
vim.api.nvim_set_option_value("previewwindow", true, { scope = "local", win = 0 })
|
|
||||||
vim.w.oil_entry_id = entry.id
|
|
||||||
vim.w.oil_source_win = prev_win
|
|
||||||
vim.api.nvim_set_current_win(prev_win)
|
|
||||||
end
|
|
||||||
open_next_entry(cb)
|
open_next_entry(cb)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
@ -677,6 +811,7 @@ end
|
||||||
---@return boolean
|
---@return boolean
|
||||||
local function maybe_hijack_directory_buffer(bufnr)
|
local function maybe_hijack_directory_buffer(bufnr)
|
||||||
local config = require("oil.config")
|
local config = require("oil.config")
|
||||||
|
local fs = require("oil.fs")
|
||||||
local util = require("oil.util")
|
local util = require("oil.util")
|
||||||
if not config.default_file_explorer then
|
if not config.default_file_explorer then
|
||||||
return false
|
return false
|
||||||
|
|
@ -688,21 +823,36 @@ local function maybe_hijack_directory_buffer(bufnr)
|
||||||
if util.parse_url(bufname) or vim.fn.isdirectory(bufname) == 0 then
|
if util.parse_url(bufname) or vim.fn.isdirectory(bufname) == 0 then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
local replaced = util.rename_buffer(
|
local new_name = util.addslash(
|
||||||
bufnr,
|
config.adapter_to_scheme.files .. fs.os_to_posix_path(vim.fn.fnamemodify(bufname, ":p"))
|
||||||
util.addslash(config.adapter_to_scheme.files .. vim.fn.fnamemodify(bufname, ":p"))
|
|
||||||
)
|
)
|
||||||
|
local replaced = util.rename_buffer(bufnr, new_name)
|
||||||
return not replaced
|
return not replaced
|
||||||
end
|
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",
|
||||||
|
|
@ -713,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",
|
||||||
|
|
@ -784,8 +974,11 @@ local function set_colors()
|
||||||
end
|
end
|
||||||
-- TODO can remove this call once we drop support for Neovim 0.8. FloatTitle was introduced as a
|
-- TODO can remove this call once we drop support for Neovim 0.8. FloatTitle was introduced as a
|
||||||
-- built-in highlight group in 0.9, and we can start to rely on colorschemes setting it.
|
-- built-in highlight group in 0.9, and we can start to rely on colorschemes setting it.
|
||||||
if not pcall(vim.api.nvim_get_hl_by_name, "FloatTitle", true) then
|
---@diagnostic disable-next-line: deprecated
|
||||||
|
if vim.fn.has("nvim-0.9") == 0 and not pcall(vim.api.nvim_get_hl_by_name, "FloatTitle", true) then
|
||||||
|
---@diagnostic disable-next-line: deprecated
|
||||||
local border = vim.api.nvim_get_hl_by_name("FloatBorder", true)
|
local border = vim.api.nvim_get_hl_by_name("FloatBorder", true)
|
||||||
|
---@diagnostic disable-next-line: deprecated
|
||||||
local normal = vim.api.nvim_get_hl_by_name("Normal", true)
|
local normal = vim.api.nvim_get_hl_by_name("Normal", true)
|
||||||
vim.api.nvim_set_hl(
|
vim.api.nvim_set_hl(
|
||||||
0,
|
0,
|
||||||
|
|
@ -840,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")
|
||||||
|
|
@ -855,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
|
||||||
|
|
@ -932,20 +1131,21 @@ end
|
||||||
|
|
||||||
local _on_key_ns = 0
|
local _on_key_ns = 0
|
||||||
---Initialize oil
|
---Initialize oil
|
||||||
---@param opts nil|table
|
---@param opts oil.setupOpts|nil
|
||||||
M.setup = function(opts)
|
M.setup = function(opts)
|
||||||
local Ringbuf = require("oil.ringbuf")
|
local Ringbuf = require("oil.ringbuf")
|
||||||
local config = require("oil.config")
|
local config = require("oil.config")
|
||||||
|
|
||||||
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]
|
||||||
|
|
@ -955,28 +1155,52 @@ 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
|
||||||
|
local mutator = require("oil.mutator")
|
||||||
|
if mutator.is_mutating() then
|
||||||
|
mutator.show_progress()
|
||||||
|
else
|
||||||
|
vim.notify("No mutation in progress", vim.log.levels.WARN)
|
||||||
|
end
|
||||||
|
return
|
||||||
else
|
else
|
||||||
i = i + 1
|
i = i + 1
|
||||||
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
|
||||||
|
|
@ -1022,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", {
|
||||||
|
|
@ -1057,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,
|
||||||
|
|
@ -1085,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
|
||||||
|
|
@ -1193,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,
|
||||||
})
|
})
|
||||||
|
|
@ -1202,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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,27 @@ local M = {}
|
||||||
---@return string|nil mode
|
---@return string|nil mode
|
||||||
local function resolve(rhs)
|
local function resolve(rhs)
|
||||||
if type(rhs) == "string" and vim.startswith(rhs, "actions.") then
|
if type(rhs) == "string" and vim.startswith(rhs, "actions.") then
|
||||||
return resolve(actions[vim.split(rhs, ".", { plain = true })[2]])
|
local action_name = vim.split(rhs, ".", { plain = true })[2]
|
||||||
|
local action = actions[action_name]
|
||||||
|
if not action then
|
||||||
|
vim.notify("[oil.nvim] Unknown action name: " .. action_name, vim.log.levels.ERROR)
|
||||||
|
end
|
||||||
|
return resolve(action)
|
||||||
elseif type(rhs) == "table" then
|
elseif type(rhs) == "table" then
|
||||||
local opts = vim.deepcopy(rhs)
|
local opts = vim.deepcopy(rhs)
|
||||||
local callback = opts.callback
|
-- We support passing in a `callback` key, or using the 1 index as the rhs of the keymap
|
||||||
|
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
|
||||||
|
|
@ -21,8 +38,24 @@ local function resolve(rhs)
|
||||||
opts = vim.tbl_extend("keep", opts, action_opts)
|
opts = vim.tbl_extend("keep", opts, action_opts)
|
||||||
mode = mode or action_mode
|
mode = mode or action_mode
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- remove all the keys that we can't pass as options to `vim.keymap.set`
|
||||||
opts.callback = nil
|
opts.callback = nil
|
||||||
opts.mode = nil
|
opts.mode = nil
|
||||||
|
opts[1] = nil
|
||||||
|
opts.deprecated = nil
|
||||||
|
opts.parameters = nil
|
||||||
|
|
||||||
|
if opts.opts and type(callback) == "function" then
|
||||||
|
local callback_args = opts.opts
|
||||||
|
opts.opts = nil
|
||||||
|
local orig_callback = callback
|
||||||
|
callback = function()
|
||||||
|
---@diagnostic disable-next-line: redundant-parameter
|
||||||
|
orig_callback(callback_args)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return callback, opts, mode
|
return callback, opts, mode
|
||||||
else
|
else
|
||||||
return rhs, {}
|
return rhs, {}
|
||||||
|
|
@ -56,31 +89,30 @@ M.show_help = function(keymaps)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local col_left = {}
|
|
||||||
local col_desc = {}
|
|
||||||
local max_lhs = 1
|
local max_lhs = 1
|
||||||
|
local keymap_entries = {}
|
||||||
for k, rhs in pairs(keymaps) do
|
for k, rhs in pairs(keymaps) do
|
||||||
local all_lhs = lhs_to_all_lhs[k]
|
local all_lhs = lhs_to_all_lhs[k]
|
||||||
if all_lhs then
|
if all_lhs then
|
||||||
local _, opts = resolve(rhs)
|
local _, opts = resolve(rhs)
|
||||||
local keystr = table.concat(all_lhs, "/")
|
local keystr = table.concat(all_lhs, "/")
|
||||||
max_lhs = math.max(max_lhs, vim.api.nvim_strwidth(keystr))
|
max_lhs = math.max(max_lhs, vim.api.nvim_strwidth(keystr))
|
||||||
table.insert(col_left, { str = keystr, all_lhs = all_lhs })
|
table.insert(keymap_entries, { str = keystr, all_lhs = all_lhs, desc = opts.desc or "" })
|
||||||
table.insert(col_desc, opts.desc or "")
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
table.sort(keymap_entries, function(a, b)
|
||||||
|
return a.desc < b.desc
|
||||||
|
end)
|
||||||
|
|
||||||
local lines = {}
|
local lines = {}
|
||||||
local highlights = {}
|
local highlights = {}
|
||||||
local max_line = 1
|
local max_line = 1
|
||||||
for i = 1, #col_left do
|
for _, entry in ipairs(keymap_entries) do
|
||||||
local left = col_left[i]
|
local line = string.format(" %s %s", util.pad_align(entry.str, max_lhs, "left"), entry.desc)
|
||||||
local desc = col_desc[i]
|
|
||||||
local line = string.format(" %s %s", util.rpad(left.str, max_lhs), 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
|
||||||
for _, key in ipairs(left.all_lhs) do
|
for _, key in ipairs(entry.all_lhs) do
|
||||||
local keywidth = vim.api.nvim_strwidth(key)
|
local keywidth = vim.api.nvim_strwidth(key)
|
||||||
table.insert(highlights, { "Special", #lines, start, start + keywidth })
|
table.insert(highlights, { "Special", #lines, start, start + keywidth })
|
||||||
start = start + keywidth + 1
|
start = start + keywidth + 1
|
||||||
|
|
@ -99,8 +131,8 @@ M.show_help = function(keymaps)
|
||||||
end
|
end
|
||||||
vim.keymap.set("n", "q", "<cmd>close<CR>", { buffer = bufnr })
|
vim.keymap.set("n", "q", "<cmd>close<CR>", { buffer = bufnr })
|
||||||
vim.keymap.set("n", "<c-c>", "<cmd>close<CR>", { buffer = bufnr })
|
vim.keymap.set("n", "<c-c>", "<cmd>close<CR>", { buffer = bufnr })
|
||||||
vim.api.nvim_buf_set_option(bufnr, "modifiable", false)
|
vim.bo[bufnr].modifiable = false
|
||||||
vim.api.nvim_buf_set_option(bufnr, "bufhidden", "wipe")
|
vim.bo[bufnr].bufhidden = "wipe"
|
||||||
|
|
||||||
local editor_width = vim.o.columns
|
local editor_width = vim.o.columns
|
||||||
local editor_height = layout.get_editor_height()
|
local editor_height = layout.get_editor_height()
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,15 @@
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
|
---@param value number
|
||||||
|
---@return boolean
|
||||||
local function is_float(value)
|
local function is_float(value)
|
||||||
local _, p = math.modf(value)
|
local _, p = math.modf(value)
|
||||||
return p ~= 0
|
return p ~= 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param value number
|
||||||
|
---@param max_value number
|
||||||
|
---@return number
|
||||||
local function calc_float(value, max_value)
|
local function calc_float(value, max_value)
|
||||||
if value and is_float(value) then
|
if value and is_float(value) then
|
||||||
return math.min(max_value, value * max_value)
|
return math.min(max_value, value * max_value)
|
||||||
|
|
@ -93,6 +98,97 @@ M.calculate_height = function(desired_height, opts)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@class (exact) oil.WinLayout
|
||||||
|
---@field width integer
|
||||||
|
---@field height integer
|
||||||
|
---@field row integer
|
||||||
|
---@field col integer
|
||||||
|
|
||||||
|
---@return vim.api.keyset.win_config
|
||||||
|
M.get_fullscreen_win_opts = function()
|
||||||
|
local config = require("oil.config")
|
||||||
|
|
||||||
|
local total_width = M.get_editor_width()
|
||||||
|
local total_height = M.get_editor_height()
|
||||||
|
local width = total_width - 2 * config.float.padding
|
||||||
|
if config.float.border ~= "none" then
|
||||||
|
width = width - 2 -- The border consumes 1 col on each side
|
||||||
|
end
|
||||||
|
if config.float.max_width > 0 then
|
||||||
|
local max_width = math.floor(calc_float(config.float.max_width, total_width))
|
||||||
|
width = math.min(width, max_width)
|
||||||
|
end
|
||||||
|
local height = total_height - 2 * config.float.padding
|
||||||
|
if config.float.max_height > 0 then
|
||||||
|
local max_height = math.floor(calc_float(config.float.max_height, total_height))
|
||||||
|
height = math.min(height, max_height)
|
||||||
|
end
|
||||||
|
local row = math.floor((total_height - height) / 2)
|
||||||
|
local col = math.floor((total_width - width) / 2) - 1 -- adjust for border width
|
||||||
|
|
||||||
|
local win_opts = {
|
||||||
|
relative = "editor",
|
||||||
|
width = width,
|
||||||
|
height = height,
|
||||||
|
row = row,
|
||||||
|
col = col,
|
||||||
|
border = config.float.border,
|
||||||
|
zindex = 45,
|
||||||
|
}
|
||||||
|
return config.float.override(win_opts) or win_opts
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param winid integer
|
||||||
|
---@param direction "above"|"below"|"left"|"right"|"auto"
|
||||||
|
---@param gap integer
|
||||||
|
---@return oil.WinLayout root_dim New dimensions of the original window
|
||||||
|
---@return oil.WinLayout new_dim New dimensions of the new window
|
||||||
|
M.split_window = function(winid, direction, gap)
|
||||||
|
if direction == "auto" then
|
||||||
|
direction = vim.o.splitright and "right" or "left"
|
||||||
|
end
|
||||||
|
|
||||||
|
local float_config = vim.api.nvim_win_get_config(winid)
|
||||||
|
---@type oil.WinLayout
|
||||||
|
local dim_root = {
|
||||||
|
width = float_config.width,
|
||||||
|
height = float_config.height,
|
||||||
|
col = float_config.col,
|
||||||
|
row = float_config.row,
|
||||||
|
}
|
||||||
|
if vim.fn.has("nvim-0.10") == 0 then
|
||||||
|
-- read https://github.com/neovim/neovim/issues/24430 for more infos.
|
||||||
|
dim_root.col = float_config.col[vim.val_idx]
|
||||||
|
dim_root.row = float_config.row[vim.val_idx]
|
||||||
|
end
|
||||||
|
local dim_new = vim.deepcopy(dim_root)
|
||||||
|
|
||||||
|
if direction == "left" or direction == "right" then
|
||||||
|
dim_new.width = math.floor(float_config.width / 2) - math.ceil(gap / 2)
|
||||||
|
dim_root.width = dim_new.width
|
||||||
|
else
|
||||||
|
dim_new.height = math.floor(float_config.height / 2) - math.ceil(gap / 2)
|
||||||
|
dim_root.height = dim_new.height
|
||||||
|
end
|
||||||
|
|
||||||
|
if direction == "left" then
|
||||||
|
dim_root.col = dim_root.col + dim_root.width + gap
|
||||||
|
elseif direction == "right" then
|
||||||
|
dim_new.col = dim_new.col + dim_new.width + gap
|
||||||
|
elseif direction == "above" then
|
||||||
|
dim_root.row = dim_root.row + dim_root.height + gap
|
||||||
|
elseif direction == "below" then
|
||||||
|
dim_new.row = dim_new.row + dim_new.height + gap
|
||||||
|
end
|
||||||
|
|
||||||
|
return dim_root, dim_new
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param desired_width integer
|
||||||
|
---@param desired_height integer
|
||||||
|
---@param opts table
|
||||||
|
---@return integer width
|
||||||
|
---@return integer height
|
||||||
M.calculate_dims = function(desired_width, desired_height, opts)
|
M.calculate_dims = function(desired_width, desired_height, opts)
|
||||||
local width = M.calculate_width(desired_width, opts)
|
local width = M.calculate_width(desired_width, opts)
|
||||||
local height = M.calculate_height(desired_height, opts)
|
local height = M.calculate_height(desired_height, opts)
|
||||||
|
|
|
||||||
|
|
@ -67,14 +67,15 @@ M.set_loading = function(bufnr, is_loading)
|
||||||
timers[bufnr] = vim.loop.new_timer()
|
timers[bufnr] = vim.loop.new_timer()
|
||||||
local bar_iter = M.get_bar_iter({ width = width })
|
local bar_iter = M.get_bar_iter({ width = width })
|
||||||
timers[bufnr]:start(
|
timers[bufnr]:start(
|
||||||
100, -- Delay the loading screen just a bit to avoid flicker
|
200, -- Delay the loading screen just a bit to avoid flicker
|
||||||
math.floor(1000 / FPS),
|
math.floor(1000 / FPS),
|
||||||
vim.schedule_wrap(function()
|
vim.schedule_wrap(function()
|
||||||
if not vim.api.nvim_buf_is_valid(bufnr) or not timers[bufnr] then
|
if not vim.api.nvim_buf_is_valid(bufnr) or not timers[bufnr] then
|
||||||
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
|
||||||
|
|
@ -1,224 +0,0 @@
|
||||||
-- LSP types copied from Neovim core to make typechecking pass
|
|
||||||
|
|
||||||
---@alias lsp.null nil
|
|
||||||
---@alias uinteger integer
|
|
||||||
---@alias lsp.decimal number
|
|
||||||
---@alias lsp.DocumentUri string
|
|
||||||
---@alias lsp.URI string
|
|
||||||
---@alias lsp.LSPObject table<string, lsp.LSPAny>
|
|
||||||
---@alias lsp.LSPArray lsp.LSPAny[]
|
|
||||||
---@alias lsp.LSPAny lsp.LSPObject|lsp.LSPArray|string|number|boolean|nil
|
|
||||||
|
|
||||||
---An identifier to refer to a change annotation stored with a workspace edit.
|
|
||||||
---@alias lsp.ChangeAnnotationIdentifier string
|
|
||||||
|
|
||||||
---A pattern kind describing if a glob pattern matches a file a folder or
|
|
||||||
---both.
|
|
||||||
---
|
|
||||||
---@since 3.16.0
|
|
||||||
---@alias lsp.FileOperationPatternKind
|
|
||||||
---| "file" # file
|
|
||||||
---| "folder" # folder
|
|
||||||
|
|
||||||
---Matching options for the file operation pattern.
|
|
||||||
---
|
|
||||||
---@since 3.16.0
|
|
||||||
---@class lsp.FileOperationPatternOptions
|
|
||||||
---The pattern should be matched ignoring casing.
|
|
||||||
---@field ignoreCase? boolean
|
|
||||||
|
|
||||||
---Describes textual changes on a text document. A TextDocumentEdit describes all changes
|
|
||||||
---on a document version Si and after they are applied move the document to version Si+1.
|
|
||||||
---So the creator of a TextDocumentEdit doesn't need to sort the array of edits or do any
|
|
||||||
---kind of ordering. However the edits must be non overlapping.
|
|
||||||
---@class lsp.TextDocumentEdit
|
|
||||||
---The text document to change.
|
|
||||||
---@field textDocument lsp.OptionalVersionedTextDocumentIdentifier
|
|
||||||
---The edits to be applied.
|
|
||||||
---
|
|
||||||
---@since 3.16.0 - support for AnnotatedTextEdit. This is guarded using a
|
|
||||||
---client capability.
|
|
||||||
---@field edits lsp.TextEdit|lsp.AnnotatedTextEdit[]
|
|
||||||
|
|
||||||
---A literal to identify a text document in the client.
|
|
||||||
---@class lsp.TextDocumentIdentifier
|
|
||||||
---The text document's uri.
|
|
||||||
---@field uri lsp.DocumentUri
|
|
||||||
|
|
||||||
---A text document identifier to optionally denote a specific version of a text document.
|
|
||||||
---@class lsp.OptionalVersionedTextDocumentIdentifier: lsp.TextDocumentIdentifier
|
|
||||||
---The version number of this document. If a versioned text document identifier
|
|
||||||
---is sent from the server to the client and the file is not open in the editor
|
|
||||||
---(the server has not received an open notification before) the server can send
|
|
||||||
---`null` to indicate that the version is unknown and the content on disk is the
|
|
||||||
---truth (as specified with document content ownership).
|
|
||||||
---@field version integer|lsp.null
|
|
||||||
|
|
||||||
---A special text edit with an additional change annotation.
|
|
||||||
---
|
|
||||||
---@since 3.16.0.
|
|
||||||
---@class lsp.AnnotatedTextEdit: lsp.TextEdit
|
|
||||||
---The actual identifier of the change annotation
|
|
||||||
---@field annotationId lsp.ChangeAnnotationIdentifier
|
|
||||||
|
|
||||||
---A workspace edit represents changes to many resources managed in the workspace. The edit
|
|
||||||
---should either provide `changes` or `documentChanges`. If documentChanges are present
|
|
||||||
---they are preferred over `changes` if the client can handle versioned document edits.
|
|
||||||
---
|
|
||||||
---Since version 3.13.0 a workspace edit can contain resource operations as well. If resource
|
|
||||||
---operations are present clients need to execute the operations in the order in which they
|
|
||||||
---are provided. So a workspace edit for example can consist of the following two changes:
|
|
||||||
---(1) a create file a.txt and (2) a text document edit which insert text into file a.txt.
|
|
||||||
---
|
|
||||||
---An invalid sequence (e.g. (1) delete file a.txt and (2) insert text into file a.txt) will
|
|
||||||
---cause failure of the operation. How the client recovers from the failure is described by
|
|
||||||
---the client capability: `workspace.workspaceEdit.failureHandling`
|
|
||||||
---@class lsp.WorkspaceEdit
|
|
||||||
---Holds changes to existing resources.
|
|
||||||
---@field changes? table<lsp.DocumentUri, lsp.TextEdit[]>
|
|
||||||
---Depending on the client capability `workspace.workspaceEdit.resourceOperations` document changes
|
|
||||||
---are either an array of `TextDocumentEdit`s to express changes to n different text documents
|
|
||||||
---where each text document edit addresses a specific version of a text document. Or it can contain
|
|
||||||
---above `TextDocumentEdit`s mixed with create, rename and delete file / folder operations.
|
|
||||||
---
|
|
||||||
---Whether a client supports versioned document edits is expressed via
|
|
||||||
---`workspace.workspaceEdit.documentChanges` client capability.
|
|
||||||
---
|
|
||||||
---If a client neither supports `documentChanges` nor `workspace.workspaceEdit.resourceOperations` then
|
|
||||||
---only plain `TextEdit`s using the `changes` property are supported.
|
|
||||||
---@field documentChanges? (lsp.TextDocumentEdit|lsp.CreateFile|lsp.RenameFile|lsp.DeleteFile)[]
|
|
||||||
---A map of change annotations that can be referenced in `AnnotatedTextEdit`s or create, rename and
|
|
||||||
---delete file / folder operations.
|
|
||||||
---
|
|
||||||
---Whether clients honor this property depends on the client capability `workspace.changeAnnotationSupport`.
|
|
||||||
---
|
|
||||||
---@since 3.16.0
|
|
||||||
---@field changeAnnotations? table<lsp.ChangeAnnotationIdentifier, lsp.ChangeAnnotation>
|
|
||||||
|
|
||||||
---Additional information that describes document changes.
|
|
||||||
---
|
|
||||||
---@since 3.16.0
|
|
||||||
---@class lsp.ChangeAnnotation
|
|
||||||
---A human-readable string describing the actual change. The string
|
|
||||||
---is rendered prominent in the user interface.
|
|
||||||
---@field label string
|
|
||||||
---A flag which indicates that user confirmation is needed
|
|
||||||
---before applying the change.
|
|
||||||
---@field needsConfirmation? boolean
|
|
||||||
---A human-readable string which is rendered less prominent in
|
|
||||||
---the user interface.
|
|
||||||
---@field description? string
|
|
||||||
|
|
||||||
---A text edit applicable to a text document.
|
|
||||||
---@class lsp.TextEdit
|
|
||||||
---The range of the text document to be manipulated. To insert
|
|
||||||
---text into a document create a range where start === end.
|
|
||||||
---@field range lsp.Range
|
|
||||||
---The string to be inserted. For delete operations use an
|
|
||||||
---empty string.
|
|
||||||
---@field newText string
|
|
||||||
|
|
||||||
---Options to create a file.
|
|
||||||
---@class lsp.CreateFileOptions
|
|
||||||
---Overwrite existing file. Overwrite wins over `ignoreIfExists`
|
|
||||||
---@field overwrite? boolean
|
|
||||||
---Ignore if exists.
|
|
||||||
---@field ignoreIfExists? boolean
|
|
||||||
|
|
||||||
---Rename file options
|
|
||||||
---@class lsp.RenameFileOptions
|
|
||||||
---Overwrite target if existing. Overwrite wins over `ignoreIfExists`
|
|
||||||
---@field overwrite? boolean
|
|
||||||
---Ignores if target exists.
|
|
||||||
---@field ignoreIfExists? boolean
|
|
||||||
|
|
||||||
---Delete file options
|
|
||||||
---@class lsp.DeleteFileOptions
|
|
||||||
---Delete the content recursively if a folder is denoted.
|
|
||||||
---@field recursive? boolean
|
|
||||||
---Ignore the operation if the file doesn't exist.
|
|
||||||
---@field ignoreIfNotExists? boolean
|
|
||||||
|
|
||||||
---A generic resource operation.
|
|
||||||
---@class lsp.ResourceOperation
|
|
||||||
---The resource operation kind.
|
|
||||||
---@field kind string
|
|
||||||
---An optional annotation identifier describing the operation.
|
|
||||||
---
|
|
||||||
---@since 3.16.0
|
|
||||||
---@field annotationId? lsp.ChangeAnnotationIdentifier
|
|
||||||
|
|
||||||
---Create file operation.
|
|
||||||
---@class lsp.CreateFile: lsp.ResourceOperation
|
|
||||||
---A create
|
|
||||||
---@field kind "create"
|
|
||||||
---The resource to create.
|
|
||||||
---@field uri lsp.DocumentUri
|
|
||||||
---Additional options
|
|
||||||
---@field options? lsp.CreateFileOptions
|
|
||||||
|
|
||||||
---Rename file operation
|
|
||||||
---@class lsp.RenameFile: lsp.ResourceOperation
|
|
||||||
---A rename
|
|
||||||
---@field kind "rename"
|
|
||||||
---The old (existing) location.
|
|
||||||
---@field oldUri lsp.DocumentUri
|
|
||||||
---The new location.
|
|
||||||
---@field newUri lsp.DocumentUri
|
|
||||||
---Rename options.
|
|
||||||
---@field options? lsp.RenameFileOptions
|
|
||||||
|
|
||||||
---Delete file operation
|
|
||||||
---@class lsp.DeleteFile: lsp.ResourceOperation
|
|
||||||
---A delete
|
|
||||||
---@field kind "delete"
|
|
||||||
---The file to delete.
|
|
||||||
---@field uri lsp.DocumentUri
|
|
||||||
---Delete options.
|
|
||||||
---@field options? lsp.DeleteFileOptions
|
|
||||||
|
|
||||||
---A pattern to describe in which file operation requests or notifications
|
|
||||||
---the server is interested in receiving.
|
|
||||||
---
|
|
||||||
---@since 3.16.0
|
|
||||||
---@class lsp.FileOperationPattern
|
|
||||||
---The glob pattern to match. Glob patterns can have the following syntax:
|
|
||||||
---- `*` to match one or more characters in a path segment
|
|
||||||
---- `?` to match on one character in a path segment
|
|
||||||
---- `**` to match any number of path segments, including none
|
|
||||||
---- `{}` to group sub patterns into an OR expression. (e.g. `**/*.{ts,js}` matches all TypeScript and JavaScript files)
|
|
||||||
---- `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …)
|
|
||||||
---- `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`)
|
|
||||||
---@field glob string
|
|
||||||
---Whether to match files or folders with this pattern.
|
|
||||||
---
|
|
||||||
---Matches both if undefined.
|
|
||||||
---@field matches? lsp.FileOperationPatternKind
|
|
||||||
---Additional options used during matching.
|
|
||||||
---@field options? lsp.FileOperationPatternOptions
|
|
||||||
|
|
||||||
---A filter to describe in which file operation requests or notifications
|
|
||||||
---the server is interested in receiving.
|
|
||||||
---
|
|
||||||
---@since 3.16.0
|
|
||||||
---@class lsp.FileOperationFilter
|
|
||||||
---A Uri scheme like `file` or `untitled`.
|
|
||||||
---@field scheme? string
|
|
||||||
---The actual file operation pattern.
|
|
||||||
---@field pattern lsp.FileOperationPattern
|
|
||||||
|
|
||||||
--- @class vim.lpeg.Pattern
|
|
||||||
--- @operator unm: vim.lpeg.Pattern
|
|
||||||
--- @operator add(vim.lpeg.Pattern): vim.lpeg.Pattern
|
|
||||||
--- @operator sub(vim.lpeg.Pattern): vim.lpeg.Pattern
|
|
||||||
--- @operator mul(vim.lpeg.Pattern): vim.lpeg.Pattern
|
|
||||||
--- @operator mul(vim.lpeg.Capture): vim.lpeg.Pattern
|
|
||||||
--- @operator div(string): vim.lpeg.Capture
|
|
||||||
--- @operator div(number): vim.lpeg.Capture
|
|
||||||
--- @operator div(table): vim.lpeg.Capture
|
|
||||||
--- @operator div(function): vim.lpeg.Capture
|
|
||||||
--- @operator pow(number): vim.lpeg.Pattern
|
|
||||||
--- @operator mod(function): nil
|
|
||||||
--- @field match fun(pattern: vim.lpeg.Pattern, subject: string, init?: integer): integer|vim.lpeg.Capture|nil
|
|
||||||
|
|
||||||
--- @alias vim.lpeg.Capture vim.lpeg.Pattern
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
local config = require("oil.config")
|
local config = require("oil.config")
|
||||||
|
local fs = require("oil.fs")
|
||||||
local util = require("oil.util")
|
local util = require("oil.util")
|
||||||
local workspace = require("oil.lsp.workspace")
|
local workspace = require("oil.lsp.workspace")
|
||||||
|
|
||||||
|
|
@ -17,23 +18,32 @@ M.will_perform_file_operations = function(actions)
|
||||||
local src_adapter = assert(config.get_adapter_by_scheme(src_scheme))
|
local src_adapter = assert(config.get_adapter_by_scheme(src_scheme))
|
||||||
local dest_scheme, dest_path = util.parse_url(action.dest_url)
|
local dest_scheme, dest_path = util.parse_url(action.dest_url)
|
||||||
local dest_adapter = assert(config.get_adapter_by_scheme(dest_scheme))
|
local dest_adapter = assert(config.get_adapter_by_scheme(dest_scheme))
|
||||||
|
src_path = fs.posix_to_os_path(src_path)
|
||||||
|
dest_path = fs.posix_to_os_path(assert(dest_path))
|
||||||
if src_adapter.name == "files" and dest_adapter.name == "files" then
|
if src_adapter.name == "files" and dest_adapter.name == "files" then
|
||||||
moves[src_path] = dest_path
|
moves[src_path] = dest_path
|
||||||
|
elseif src_adapter.name == "files" then
|
||||||
|
table.insert(deletes, src_path)
|
||||||
|
elseif dest_adapter.name == "files" then
|
||||||
|
table.insert(creates, src_path)
|
||||||
end
|
end
|
||||||
elseif action.type == "create" then
|
elseif action.type == "create" then
|
||||||
local scheme, path = util.parse_url(action.url)
|
local scheme, path = util.parse_url(action.url)
|
||||||
|
path = fs.posix_to_os_path(assert(path))
|
||||||
local adapter = assert(config.get_adapter_by_scheme(scheme))
|
local adapter = assert(config.get_adapter_by_scheme(scheme))
|
||||||
if adapter.name == "files" then
|
if adapter.name == "files" then
|
||||||
table.insert(creates, path)
|
table.insert(creates, path)
|
||||||
end
|
end
|
||||||
elseif action.type == "delete" then
|
elseif action.type == "delete" then
|
||||||
local scheme, path = util.parse_url(action.url)
|
local scheme, path = util.parse_url(action.url)
|
||||||
|
path = fs.posix_to_os_path(assert(path))
|
||||||
local adapter = assert(config.get_adapter_by_scheme(scheme))
|
local adapter = assert(config.get_adapter_by_scheme(scheme))
|
||||||
if adapter.name == "files" then
|
if adapter.name == "files" then
|
||||||
table.insert(deletes, path)
|
table.insert(deletes, path)
|
||||||
end
|
end
|
||||||
elseif action.type == "copy" then
|
elseif action.type == "copy" then
|
||||||
local scheme, path = util.parse_url(action.dest_url)
|
local scheme, path = util.parse_url(action.dest_url)
|
||||||
|
path = fs.posix_to_os_path(assert(path))
|
||||||
local adapter = assert(config.get_adapter_by_scheme(scheme))
|
local adapter = assert(config.get_adapter_by_scheme(scheme))
|
||||||
if adapter.name == "files" then
|
if adapter.name == "files" then
|
||||||
table.insert(creates, path)
|
table.insert(creates, path)
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,12 @@ end
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
---@param method string
|
---@param method string
|
||||||
---@return lsp.Client[]
|
---@return vim.lsp.Client[]
|
||||||
local function get_clients(method)
|
local function get_clients(method)
|
||||||
if vim.fn.has("nvim-0.10") == 1 then
|
if vim.fn.has("nvim-0.10") == 1 then
|
||||||
return vim.lsp.get_clients({ method = method })
|
return vim.lsp.get_clients({ method = method })
|
||||||
else
|
else
|
||||||
|
---@diagnostic disable-next-line: deprecated
|
||||||
local clients = vim.lsp.get_active_clients()
|
local clients = vim.lsp.get_active_clients()
|
||||||
return vim.tbl_filter(function(client)
|
return vim.tbl_filter(function(client)
|
||||||
return client.supports_method(method)
|
return client.supports_method(method)
|
||||||
|
|
@ -47,7 +48,7 @@ local function match_glob(glob, path)
|
||||||
return match >= 0
|
return match >= 0
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param client lsp.Client
|
---@param client vim.lsp.Client
|
||||||
---@param filters nil|lsp.FileOperationFilter[]
|
---@param filters nil|lsp.FileOperationFilter[]
|
||||||
---@param paths string[]
|
---@param paths string[]
|
||||||
---@return nil|string[]
|
---@return nil|string[]
|
||||||
|
|
@ -65,8 +66,39 @@ local function get_matching_paths(client, filters, paths)
|
||||||
if ignore_case then
|
if ignore_case then
|
||||||
glob = glob:lower()
|
glob = glob:lower()
|
||||||
end
|
end
|
||||||
---@type nil|vim.lpeg.Pattern
|
|
||||||
local glob_pattern = vim.glob and vim.glob.to_lpeg and vim.glob.to_lpeg(glob)
|
-- Some language servers use forward slashes as path separators on Windows (LuaLS)
|
||||||
|
-- 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("/", "\\")
|
||||||
|
end
|
||||||
|
|
||||||
|
---@type string|vim.lpeg.Pattern
|
||||||
|
local glob_to_match = glob
|
||||||
|
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
|
||||||
|
-- find alternations and sort them by length to try to match the longest first
|
||||||
|
if vim.fn.has("nvim-0.11") == 0 then
|
||||||
|
table.sort(filtered, function(a, b)
|
||||||
|
return a:len() > b:len()
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
return "{" .. table.concat(filtered, ",") .. "}"
|
||||||
|
end)
|
||||||
|
|
||||||
|
glob_to_match = vim.glob.to_lpeg(glob)
|
||||||
|
end
|
||||||
local matches = pattern.matches
|
local matches = pattern.matches
|
||||||
table.insert(match_fns, function(path)
|
table.insert(match_fns, function(path)
|
||||||
local is_dir = vim.fn.isdirectory(path) == 1
|
local is_dir = vim.fn.isdirectory(path) == 1
|
||||||
|
|
@ -77,7 +109,7 @@ local function get_matching_paths(client, filters, paths)
|
||||||
if ignore_case then
|
if ignore_case then
|
||||||
path = path:lower()
|
path = path:lower()
|
||||||
end
|
end
|
||||||
return match_glob(glob_pattern or glob, path)
|
return match_glob(glob_to_match, path)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -145,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)
|
||||||
|
|
@ -182,8 +219,12 @@ 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)
|
client:notify(method, params)
|
||||||
|
else
|
||||||
|
---@diagnostic disable-next-line: param-type-mismatch
|
||||||
|
client.notify(method, params)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -258,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
|
||||||
client.request_sync(ms.workspace_willRenameFiles, params, options.timeout_ms or 1000, 0)
|
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)
|
||||||
|
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)
|
||||||
|
|
@ -291,8 +338,12 @@ 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)
|
client:notify(ms.workspace_didRenameFiles, params)
|
||||||
|
else
|
||||||
|
---@diagnostic disable-next-line: param-type-mismatch
|
||||||
|
client.notify(ms.workspace_didRenameFiles, params)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -45,11 +45,12 @@ end
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
---@param lines string[]
|
---@param lines string[]
|
||||||
local function render_lines(winid, bufnr, lines)
|
local function render_lines(winid, bufnr, lines)
|
||||||
util.render_text(
|
util.render_text(bufnr, lines, {
|
||||||
bufnr,
|
v_align = "top",
|
||||||
lines,
|
h_align = "left",
|
||||||
{ v_align = "top", h_align = "left", winid = winid, actions = { "[O]k", "[C]ancel" } }
|
winid = winid,
|
||||||
)
|
actions = { "[Y]es", "[N]o" },
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param actions oil.Action[]
|
---@param actions oil.Action[]
|
||||||
|
|
@ -81,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
|
||||||
|
|
@ -90,7 +93,7 @@ M.show = vim.schedule_wrap(function(actions, should_confirm, cb)
|
||||||
table.insert(lines, "")
|
table.insert(lines, "")
|
||||||
|
|
||||||
-- Create the floating window
|
-- Create the floating window
|
||||||
local width, height = layout.calculate_dims(max_line_width, #lines + 1, config.preview)
|
local width, height = layout.calculate_dims(max_line_width, #lines + 1, config.confirmation)
|
||||||
local ok, winid = pcall(vim.api.nvim_open_win, bufnr, true, {
|
local ok, winid = pcall(vim.api.nvim_open_win, bufnr, true, {
|
||||||
relative = "editor",
|
relative = "editor",
|
||||||
width = width,
|
width = width,
|
||||||
|
|
@ -99,7 +102,7 @@ M.show = vim.schedule_wrap(function(actions, should_confirm, cb)
|
||||||
col = math.floor((layout.get_editor_width() - width) / 2),
|
col = math.floor((layout.get_editor_width() - width) / 2),
|
||||||
zindex = 152, -- render on top of the floating window title
|
zindex = 152, -- render on top of the floating window title
|
||||||
style = "minimal",
|
style = "minimal",
|
||||||
border = config.preview.border,
|
border = config.confirmation.border,
|
||||||
})
|
})
|
||||||
if not ok then
|
if not ok then
|
||||||
vim.notify(string.format("Error showing oil preview window: %s", winid), vim.log.levels.ERROR)
|
vim.notify(string.format("Error showing oil preview window: %s", winid), vim.log.levels.ERROR)
|
||||||
|
|
@ -107,12 +110,14 @@ M.show = vim.schedule_wrap(function(actions, should_confirm, cb)
|
||||||
end
|
end
|
||||||
vim.bo[bufnr].filetype = "oil_preview"
|
vim.bo[bufnr].filetype = "oil_preview"
|
||||||
vim.bo[bufnr].syntax = "oil_preview"
|
vim.bo[bufnr].syntax = "oil_preview"
|
||||||
for k, v in pairs(config.preview.win_options) do
|
for k, v in pairs(config.confirmation.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
|
||||||
|
|
||||||
render_lines(winid, bufnr, lines)
|
render_lines(winid, bufnr, lines)
|
||||||
|
|
||||||
|
local restore_cursor = util.hide_cursor()
|
||||||
|
|
||||||
-- Attach autocmds and keymaps
|
-- Attach autocmds and keymaps
|
||||||
local cancel
|
local cancel
|
||||||
local confirm
|
local confirm
|
||||||
|
|
@ -126,6 +131,7 @@ M.show = vim.schedule_wrap(function(actions, should_confirm, cb)
|
||||||
end
|
end
|
||||||
autocmds = {}
|
autocmds = {}
|
||||||
vim.api.nvim_win_close(winid, true)
|
vim.api.nvim_win_close(winid, true)
|
||||||
|
restore_cursor()
|
||||||
cb(value)
|
cb(value)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -151,7 +157,7 @@ M.show = vim.schedule_wrap(function(actions, should_confirm, cb)
|
||||||
vim.api.nvim_create_autocmd("VimResized", {
|
vim.api.nvim_create_autocmd("VimResized", {
|
||||||
callback = function()
|
callback = function()
|
||||||
if vim.api.nvim_win_is_valid(winid) then
|
if vim.api.nvim_win_is_valid(winid) then
|
||||||
width, height = layout.calculate_dims(max_line_width, #lines, config.preview)
|
width, height = layout.calculate_dims(max_line_width, #lines, config.confirmation)
|
||||||
vim.api.nvim_win_set_config(winid, {
|
vim.api.nvim_win_set_config(winid, {
|
||||||
relative = "editor",
|
relative = "editor",
|
||||||
width = width,
|
width = width,
|
||||||
|
|
@ -165,17 +171,22 @@ M.show = vim.schedule_wrap(function(actions, should_confirm, cb)
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
for _, cancel_key in ipairs({ "q", "C", "c", "<C-c>", "<Esc>" }) do
|
|
||||||
|
-- We used to use [C]ancel to cancel, so preserve the old keymap
|
||||||
|
local cancel_keys = { "n", "N", "c", "C", "q", "<C-c>", "<Esc>" }
|
||||||
|
for _, cancel_key in ipairs(cancel_keys) do
|
||||||
vim.keymap.set("n", cancel_key, function()
|
vim.keymap.set("n", cancel_key, function()
|
||||||
cancel()
|
cancel()
|
||||||
end, { buffer = bufnr, nowait = true })
|
end, { buffer = bufnr, nowait = true })
|
||||||
end
|
end
|
||||||
vim.keymap.set("n", "O", function()
|
|
||||||
confirm()
|
-- We used to use [O]k to confirm, so preserve the old keymap
|
||||||
end, { buffer = bufnr })
|
local confirm_keys = { "y", "Y", "o", "O" }
|
||||||
vim.keymap.set("n", "o", function()
|
for _, confirm_key in ipairs(confirm_keys) do
|
||||||
confirm()
|
vim.keymap.set("n", confirm_key, function()
|
||||||
end, { buffer = bufnr })
|
confirm()
|
||||||
|
end, { buffer = bufnr, nowait = true })
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|
@ -3,11 +3,12 @@ local Trie = require("oil.mutator.trie")
|
||||||
local cache = require("oil.cache")
|
local cache = require("oil.cache")
|
||||||
local columns = require("oil.columns")
|
local columns = require("oil.columns")
|
||||||
local config = require("oil.config")
|
local config = require("oil.config")
|
||||||
|
local confirmation = require("oil.mutator.confirmation")
|
||||||
local constants = require("oil.constants")
|
local constants = require("oil.constants")
|
||||||
|
local fs = require("oil.fs")
|
||||||
local lsp_helpers = require("oil.lsp.helpers")
|
local lsp_helpers = require("oil.lsp.helpers")
|
||||||
local oil = require("oil")
|
local oil = require("oil")
|
||||||
local parser = require("oil.mutator.parser")
|
local parser = require("oil.mutator.parser")
|
||||||
local preview = require("oil.mutator.preview")
|
|
||||||
local util = require("oil.util")
|
local util = require("oil.util")
|
||||||
local view = require("oil.view")
|
local view = require("oil.view")
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
@ -61,15 +62,30 @@ M.create_actions_from_diffs = function(all_diffs)
|
||||||
return list
|
return list
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
-- To deduplicate create actions
|
||||||
|
-- This can happen when creating deep nested files e.g.
|
||||||
|
-- > foo/bar/a.txt
|
||||||
|
-- > foo/bar/b.txt
|
||||||
|
local seen_creates = {}
|
||||||
|
|
||||||
---@param action oil.Action
|
---@param action oil.Action
|
||||||
local function add_action(action)
|
local function add_action(action)
|
||||||
local adapter = assert(config.get_adapter_by_scheme(action.dest_url or action.url))
|
local adapter = assert(config.get_adapter_by_scheme(action.dest_url or action.url))
|
||||||
if not adapter.filter_action or adapter.filter_action(action) then
|
if not adapter.filter_action or adapter.filter_action(action) then
|
||||||
|
if action.type == "create" then
|
||||||
|
if seen_creates[action.url] then
|
||||||
|
return
|
||||||
|
else
|
||||||
|
seen_creates[action.url] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
table.insert(actions, action)
|
table.insert(actions, action)
|
||||||
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
|
||||||
|
|
@ -84,7 +100,8 @@ M.create_actions_from_diffs = function(all_diffs)
|
||||||
table.insert(by_id, diff)
|
table.insert(by_id, diff)
|
||||||
else
|
else
|
||||||
-- Parse nested files like foo/bar/baz
|
-- Parse nested files like foo/bar/baz
|
||||||
local pieces = vim.split(diff.name, "/")
|
local path_sep = fs.is_windows and "[/\\]" or "/"
|
||||||
|
local pieces = vim.split(diff.name, path_sep)
|
||||||
local url = parent_url:gsub("/$", "")
|
local url = parent_url:gsub("/$", "")
|
||||||
for i, v in ipairs(pieces) do
|
for i, v in ipairs(pieces) do
|
||||||
local is_last = i == #pieces
|
local is_last = i == #pieces
|
||||||
|
|
@ -123,6 +140,7 @@ M.create_actions_from_diffs = function(all_diffs)
|
||||||
else
|
else
|
||||||
local by_id = diff_by_id[diff.id]
|
local by_id = diff_by_id[diff.id]
|
||||||
-- HACK: set has_delete field on a list-like table of diffs
|
-- HACK: set has_delete field on a list-like table of diffs
|
||||||
|
---@diagnostic disable-next-line: inject-field
|
||||||
by_id.has_delete = true
|
by_id.has_delete = true
|
||||||
-- Don't insert the delete. We already know that there is a delete because of the presence
|
-- Don't insert the delete. We already know that there is a delete because of the presence
|
||||||
-- in the diff_by_id map. The list will only include the 'new' diffs.
|
-- in the diff_by_id map. The list will only include the 'new' diffs.
|
||||||
|
|
@ -235,11 +253,10 @@ M.enforce_action_order = function(actions)
|
||||||
-- Process children before moving
|
-- Process children before moving
|
||||||
-- e.g. NEW /a/b BEFORE MOVE /a -> /b
|
-- e.g. NEW /a/b BEFORE MOVE /a -> /b
|
||||||
dest_trie:accum_children_of(action.src_url, ret)
|
dest_trie:accum_children_of(action.src_url, ret)
|
||||||
-- Copy children before moving parent dir
|
-- Process children before moving parent dir
|
||||||
-- e.g. COPY /a/b -> /b BEFORE MOVE /a -> /d
|
-- e.g. COPY /a/b -> /b BEFORE MOVE /a -> /d
|
||||||
src_trie:accum_children_of(action.src_url, ret, function(a)
|
-- e.g. CHANGE /a/b BEFORE MOVE /a -> /d
|
||||||
return a.type == "copy"
|
src_trie:accum_children_of(action.src_url, ret)
|
||||||
end)
|
|
||||||
-- Process remove path before moving to new path
|
-- Process remove path before moving to new path
|
||||||
-- e.g. MOVE /a -> /b BEFORE MOVE /c -> /a
|
-- e.g. MOVE /a -> /b BEFORE MOVE /c -> /a
|
||||||
src_trie:accum_actions_at(action.dest_url, ret, function(a)
|
src_trie:accum_actions_at(action.dest_url, ret, function(a)
|
||||||
|
|
@ -364,6 +381,8 @@ M.enforce_action_order = function(actions)
|
||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local progress
|
||||||
|
|
||||||
---@param actions oil.Action[]
|
---@param actions oil.Action[]
|
||||||
---@param cb fun(err: nil|string)
|
---@param cb fun(err: nil|string)
|
||||||
M.process_actions = function(actions, cb)
|
M.process_actions = function(actions, cb)
|
||||||
|
|
@ -371,7 +390,11 @@ M.process_actions = function(actions, cb)
|
||||||
"User",
|
"User",
|
||||||
{ pattern = "OilActionsPre", modeline = false, data = { actions = actions } }
|
{ pattern = "OilActionsPre", modeline = false, data = { actions = actions } }
|
||||||
)
|
)
|
||||||
local did_complete = lsp_helpers.will_perform_file_operations(actions)
|
|
||||||
|
local did_complete = nil
|
||||||
|
if config.lsp_file_methods.enabled then
|
||||||
|
did_complete = lsp_helpers.will_perform_file_operations(actions)
|
||||||
|
end
|
||||||
|
|
||||||
-- Convert some cross-adapter moves to a copy + delete
|
-- Convert some cross-adapter moves to a copy + delete
|
||||||
for _, action in ipairs(actions) do
|
for _, action in ipairs(actions) do
|
||||||
|
|
@ -379,6 +402,7 @@ M.process_actions = function(actions, cb)
|
||||||
local _, cross_action = util.get_adapter_for_action(action)
|
local _, cross_action = util.get_adapter_for_action(action)
|
||||||
-- Only do the conversion if the cross-adapter support is "copy"
|
-- Only do the conversion if the cross-adapter support is "copy"
|
||||||
if cross_action == "copy" then
|
if cross_action == "copy" then
|
||||||
|
---@diagnostic disable-next-line: assign-type-mismatch
|
||||||
action.type = "copy"
|
action.type = "copy"
|
||||||
table.insert(actions, {
|
table.insert(actions, {
|
||||||
type = "delete",
|
type = "delete",
|
||||||
|
|
@ -390,11 +414,12 @@ M.process_actions = function(actions, cb)
|
||||||
end
|
end
|
||||||
|
|
||||||
local finished = false
|
local finished = false
|
||||||
local progress = Progress.new()
|
progress = Progress.new()
|
||||||
local function finish(err)
|
local function finish(err)
|
||||||
if not finished then
|
if not finished then
|
||||||
finished = true
|
finished = true
|
||||||
progress:close()
|
progress:close()
|
||||||
|
progress = nil
|
||||||
vim.api.nvim_exec_autocmds(
|
vim.api.nvim_exec_autocmds(
|
||||||
"User",
|
"User",
|
||||||
{ pattern = "OilActionsPost", modeline = false, data = { err = err, actions = actions } }
|
{ pattern = "OilActionsPost", modeline = false, data = { err = err, actions = actions } }
|
||||||
|
|
@ -423,7 +448,9 @@ M.process_actions = function(actions, cb)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
if idx > #actions then
|
if idx > #actions then
|
||||||
did_complete()
|
if did_complete then
|
||||||
|
did_complete()
|
||||||
|
end
|
||||||
finish()
|
finish()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
@ -455,6 +482,12 @@ M.process_actions = function(actions, cb)
|
||||||
next_action()
|
next_action()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
M.show_progress = function()
|
||||||
|
if progress then
|
||||||
|
progress:restore()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local mutation_in_progress = false
|
local mutation_in_progress = false
|
||||||
|
|
||||||
---@return boolean
|
---@return boolean
|
||||||
|
|
@ -486,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
|
||||||
|
|
@ -520,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)
|
||||||
vim.api.nvim_win_set_buf(0, bufnr)
|
assert(errs)
|
||||||
pcall(vim.api.nvim_win_set_cursor, 0, { errs[1].lnum + 1, errs[1].col })
|
-- 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)
|
||||||
|
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")
|
||||||
|
|
@ -531,7 +569,7 @@ M.try_write_changes = function(confirm, cb)
|
||||||
end
|
end
|
||||||
|
|
||||||
local actions = M.create_actions_from_diffs(all_diffs)
|
local actions = M.create_actions_from_diffs(all_diffs)
|
||||||
preview.show(actions, confirm, function(proceed)
|
confirmation.show(actions, confirm, function(proceed)
|
||||||
if not proceed then
|
if not proceed then
|
||||||
unlock()
|
unlock()
|
||||||
cb("Canceled")
|
cb("Canceled")
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ local FIELD_META = constants.FIELD_META
|
||||||
---@field type "delete"
|
---@field type "delete"
|
||||||
---@field name string
|
---@field name string
|
||||||
---@field id integer
|
---@field id integer
|
||||||
---
|
|
||||||
---@class (exact) oil.DiffChange
|
---@class (exact) oil.DiffChange
|
||||||
---@field type "change"
|
---@field type "change"
|
||||||
---@field entry_type oil.EntryType
|
---@field entry_type oil.EntryType
|
||||||
|
|
@ -37,7 +37,7 @@ local FIELD_META = constants.FIELD_META
|
||||||
---@return string
|
---@return string
|
||||||
---@return boolean
|
---@return boolean
|
||||||
local function parsedir(name)
|
local function parsedir(name)
|
||||||
local isdir = vim.endswith(name, "/")
|
local isdir = vim.endswith(name, "/") or (fs.is_windows and vim.endswith(name, "\\"))
|
||||||
if isdir then
|
if isdir then
|
||||||
name = name:sub(1, name:len() - 1)
|
name = name:sub(1, name:len() - 1)
|
||||||
end
|
end
|
||||||
|
|
@ -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,
|
||||||
|
|
@ -192,112 +192,119 @@ M.parse = function(bufnr)
|
||||||
seen_names[name] = true
|
seen_names[name] = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
for i, line in ipairs(lines) do
|
for i, line in ipairs(lines) do
|
||||||
if line:match("^/%d+") then
|
-- hack to be compatible with Lua 5.1
|
||||||
-- Parse the line for an existing entry
|
-- use return instead of goto
|
||||||
local result, err = M.parse_line(adapter, line, column_defs)
|
(function()
|
||||||
if not result or err then
|
if line:match("^/%d+") then
|
||||||
table.insert(errors, {
|
-- Parse the line for an existing entry
|
||||||
message = err,
|
local result, err = M.parse_line(adapter, line, column_defs)
|
||||||
lnum = i - 1,
|
if not result or err then
|
||||||
end_lnum = i,
|
table.insert(errors, {
|
||||||
col = 0,
|
message = err,
|
||||||
})
|
lnum = i - 1,
|
||||||
goto continue
|
end_lnum = i,
|
||||||
elseif result.data.id == 0 then
|
col = 0,
|
||||||
-- Ignore entries with ID 0 (typically the "../" entry)
|
|
||||||
goto continue
|
|
||||||
end
|
|
||||||
local parsed_entry = result.data
|
|
||||||
local entry = result.entry
|
|
||||||
if not parsed_entry.name or parsed_entry.name:match("/") or not entry then
|
|
||||||
local message
|
|
||||||
if not parsed_entry.name then
|
|
||||||
message = "No filename found"
|
|
||||||
elseif not entry then
|
|
||||||
message = "Could not find existing entry (was the ID changed?)"
|
|
||||||
else
|
|
||||||
message = "Filename cannot contain '/'"
|
|
||||||
end
|
|
||||||
table.insert(errors, {
|
|
||||||
message = message,
|
|
||||||
lnum = i - 1,
|
|
||||||
end_lnum = i,
|
|
||||||
col = 0,
|
|
||||||
})
|
|
||||||
goto continue
|
|
||||||
end
|
|
||||||
check_dupe(parsed_entry.name, i)
|
|
||||||
local meta = entry[FIELD_META]
|
|
||||||
if original_entries[parsed_entry.name] == parsed_entry.id then
|
|
||||||
if entry[FIELD_TYPE] == "link" and not compare_link_target(meta, parsed_entry) then
|
|
||||||
table.insert(diffs, {
|
|
||||||
type = "new",
|
|
||||||
name = parsed_entry.name,
|
|
||||||
entry_type = "link",
|
|
||||||
link = parsed_entry.link_target,
|
|
||||||
})
|
})
|
||||||
elseif entry[FIELD_TYPE] ~= parsed_entry._type then
|
return
|
||||||
|
elseif result.data.id == 0 then
|
||||||
|
-- Ignore entries with ID 0 (typically the "../" entry)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local parsed_entry = result.data
|
||||||
|
local entry = result.entry
|
||||||
|
|
||||||
|
local err_message
|
||||||
|
if not parsed_entry.name then
|
||||||
|
err_message = "No filename found"
|
||||||
|
elseif not entry then
|
||||||
|
err_message = "Could not find existing entry (was the ID changed?)"
|
||||||
|
elseif parsed_entry.name:match("/") or parsed_entry.name:match(fs.sep) then
|
||||||
|
err_message = "Filename cannot contain path separator"
|
||||||
|
end
|
||||||
|
if err_message then
|
||||||
|
table.insert(errors, {
|
||||||
|
message = err_message,
|
||||||
|
lnum = i - 1,
|
||||||
|
end_lnum = i,
|
||||||
|
col = 0,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
end
|
||||||
|
assert(entry)
|
||||||
|
|
||||||
|
check_dupe(parsed_entry.name, i)
|
||||||
|
local meta = entry[FIELD_META]
|
||||||
|
if original_entries[parsed_entry.name] == parsed_entry.id then
|
||||||
|
if entry[FIELD_TYPE] == "link" and not compare_link_target(meta, parsed_entry) then
|
||||||
|
table.insert(diffs, {
|
||||||
|
type = "new",
|
||||||
|
name = parsed_entry.name,
|
||||||
|
entry_type = "link",
|
||||||
|
link = parsed_entry.link_target,
|
||||||
|
})
|
||||||
|
elseif entry[FIELD_TYPE] ~= parsed_entry._type then
|
||||||
|
table.insert(diffs, {
|
||||||
|
type = "new",
|
||||||
|
name = parsed_entry.name,
|
||||||
|
entry_type = parsed_entry._type,
|
||||||
|
})
|
||||||
|
else
|
||||||
|
original_entries[parsed_entry.name] = nil
|
||||||
|
end
|
||||||
|
else
|
||||||
table.insert(diffs, {
|
table.insert(diffs, {
|
||||||
type = "new",
|
type = "new",
|
||||||
name = parsed_entry.name,
|
name = parsed_entry.name,
|
||||||
entry_type = parsed_entry._type,
|
entry_type = parsed_entry._type,
|
||||||
|
id = parsed_entry.id,
|
||||||
|
link = parsed_entry.link_target,
|
||||||
})
|
})
|
||||||
else
|
end
|
||||||
original_entries[parsed_entry.name] = nil
|
|
||||||
|
for _, col_def in ipairs(column_defs) do
|
||||||
|
local col_name = util.split_config(col_def)
|
||||||
|
if columns.compare(adapter, col_name, entry, parsed_entry[col_name]) then
|
||||||
|
table.insert(diffs, {
|
||||||
|
type = "change",
|
||||||
|
name = parsed_entry.name,
|
||||||
|
entry_type = entry[FIELD_TYPE],
|
||||||
|
column = col_name,
|
||||||
|
value = parsed_entry[col_name],
|
||||||
|
})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
table.insert(diffs, {
|
-- Parse a new entry
|
||||||
type = "new",
|
local name, isdir = parsedir(vim.trim(line))
|
||||||
name = parsed_entry.name,
|
if vim.startswith(name, "/") then
|
||||||
entry_type = parsed_entry._type,
|
table.insert(errors, {
|
||||||
id = parsed_entry.id,
|
message = "Paths cannot start with '/'",
|
||||||
link = parsed_entry.link_target,
|
lnum = i - 1,
|
||||||
})
|
end_lnum = i,
|
||||||
end
|
col = 0,
|
||||||
|
})
|
||||||
for _, col_def in ipairs(column_defs) do
|
return
|
||||||
local col_name = util.split_config(col_def)
|
end
|
||||||
if columns.compare(adapter, col_name, entry, parsed_entry[col_name]) then
|
if name ~= "" then
|
||||||
|
local link_pieces = vim.split(name, " -> ", { plain = true })
|
||||||
|
local entry_type = isdir and "directory" or "file"
|
||||||
|
local link
|
||||||
|
if #link_pieces == 2 then
|
||||||
|
entry_type = "link"
|
||||||
|
name, link = unpack(link_pieces)
|
||||||
|
end
|
||||||
|
check_dupe(name, i)
|
||||||
table.insert(diffs, {
|
table.insert(diffs, {
|
||||||
type = "change",
|
type = "new",
|
||||||
name = parsed_entry.name,
|
name = name,
|
||||||
entry_type = entry[FIELD_TYPE],
|
entry_type = entry_type,
|
||||||
column = col_name,
|
link = link,
|
||||||
value = parsed_entry[col_name],
|
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
end)()
|
||||||
-- Parse a new entry
|
|
||||||
local name, isdir = parsedir(vim.trim(line))
|
|
||||||
if vim.startswith(name, "/") then
|
|
||||||
table.insert(errors, {
|
|
||||||
message = "Paths cannot start with '/'",
|
|
||||||
lnum = i - 1,
|
|
||||||
end_lnum = i,
|
|
||||||
col = 0,
|
|
||||||
})
|
|
||||||
goto continue
|
|
||||||
end
|
|
||||||
if name ~= "" then
|
|
||||||
local link_pieces = vim.split(name, " -> ", { plain = true })
|
|
||||||
local entry_type = isdir and "directory" or "file"
|
|
||||||
local link
|
|
||||||
if #link_pieces == 2 then
|
|
||||||
entry_type = "link"
|
|
||||||
name, link = unpack(link_pieces)
|
|
||||||
end
|
|
||||||
check_dupe(name, i)
|
|
||||||
table.insert(diffs, {
|
|
||||||
type = "new",
|
|
||||||
name = name,
|
|
||||||
entry_type = entry_type,
|
|
||||||
link = link,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
::continue::
|
|
||||||
end
|
end
|
||||||
|
|
||||||
for name, child_id in pairs(original_entries) do
|
for name, child_id in pairs(original_entries) do
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,11 @@ local Progress = {}
|
||||||
local FPS = 20
|
local FPS = 20
|
||||||
|
|
||||||
function Progress.new()
|
function Progress.new()
|
||||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
|
||||||
vim.bo[bufnr].bufhidden = "wipe"
|
|
||||||
return setmetatable({
|
return setmetatable({
|
||||||
lines = { "", "" },
|
lines = { "", "" },
|
||||||
count = "",
|
count = "",
|
||||||
spinner = "",
|
spinner = "",
|
||||||
bufnr = bufnr,
|
bufnr = nil,
|
||||||
winid = nil,
|
winid = nil,
|
||||||
min_bufnr = nil,
|
min_bufnr = nil,
|
||||||
min_winid = nil,
|
min_winid = nil,
|
||||||
|
|
@ -25,6 +23,15 @@ function Progress.new()
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
---@return boolean
|
||||||
|
function Progress:is_minimized()
|
||||||
|
return not self.closing
|
||||||
|
and not self.bufnr
|
||||||
|
and self.min_bufnr
|
||||||
|
and vim.api.nvim_buf_is_valid(self.min_bufnr)
|
||||||
|
end
|
||||||
|
|
||||||
---@param opts nil|table
|
---@param opts nil|table
|
||||||
--- cancel fun()
|
--- cancel fun()
|
||||||
function Progress:show(opts)
|
function Progress:show(opts)
|
||||||
|
|
@ -32,20 +39,24 @@ function Progress:show(opts)
|
||||||
if self.winid and vim.api.nvim_win_is_valid(self.winid) then
|
if self.winid and vim.api.nvim_win_is_valid(self.winid) then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
self.closing = false
|
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||||
self.cancel = opts.cancel
|
vim.bo[bufnr].bufhidden = "wipe"
|
||||||
|
self.bufnr = bufnr
|
||||||
|
self.cancel = opts.cancel or self.cancel
|
||||||
local loading_iter = loading.get_bar_iter()
|
local loading_iter = loading.get_bar_iter()
|
||||||
local spinner = loading.get_iter("dots")
|
local spinner = loading.get_iter("dots")
|
||||||
self.timer = vim.loop.new_timer()
|
if not self.timer then
|
||||||
self.timer:start(
|
self.timer = vim.loop.new_timer()
|
||||||
0,
|
self.timer:start(
|
||||||
math.floor(1000 / FPS),
|
0,
|
||||||
vim.schedule_wrap(function()
|
math.floor(1000 / FPS),
|
||||||
self.lines[2] = string.format("%s %s", self.count, loading_iter())
|
vim.schedule_wrap(function()
|
||||||
self.spinner = spinner()
|
self.lines[2] = string.format("%s %s", self.count, loading_iter())
|
||||||
self:_render()
|
self.spinner = spinner()
|
||||||
end)
|
self:_render()
|
||||||
)
|
end)
|
||||||
|
)
|
||||||
|
end
|
||||||
local width, height = layout.calculate_dims(120, 10, config.progress)
|
local width, height = layout.calculate_dims(120, 10, config.progress)
|
||||||
self.winid = vim.api.nvim_open_win(self.bufnr, true, {
|
self.winid = vim.api.nvim_open_win(self.bufnr, true, {
|
||||||
relative = "editor",
|
relative = "editor",
|
||||||
|
|
@ -58,7 +69,7 @@ function Progress:show(opts)
|
||||||
border = config.progress.border,
|
border = config.progress.border,
|
||||||
})
|
})
|
||||||
vim.bo[self.bufnr].filetype = "oil_progress"
|
vim.bo[self.bufnr].filetype = "oil_progress"
|
||||||
for k, v in pairs(config.preview.win_options) do
|
for k, v in pairs(config.progress.win_options) do
|
||||||
vim.api.nvim_set_option_value(k, v, { scope = "local", win = self.winid })
|
vim.api.nvim_set_option_value(k, v, { scope = "local", win = self.winid })
|
||||||
end
|
end
|
||||||
table.insert(
|
table.insert(
|
||||||
|
|
@ -89,6 +100,16 @@ function Progress:show(opts)
|
||||||
vim.keymap.set("n", "M", minimize, { buffer = self.bufnr, nowait = true })
|
vim.keymap.set("n", "M", minimize, { buffer = self.bufnr, nowait = true })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Progress:restore()
|
||||||
|
if self.closing then
|
||||||
|
return
|
||||||
|
elseif not self:is_minimized() then
|
||||||
|
error("Cannot restore progress window: not minimized")
|
||||||
|
end
|
||||||
|
self:_cleanup_minimized_win()
|
||||||
|
self:show()
|
||||||
|
end
|
||||||
|
|
||||||
function Progress:_render()
|
function Progress:_render()
|
||||||
if self.bufnr and vim.api.nvim_buf_is_valid(self.bufnr) then
|
if self.bufnr and vim.api.nvim_buf_is_valid(self.bufnr) then
|
||||||
util.render_text(
|
util.render_text(
|
||||||
|
|
@ -139,6 +160,14 @@ function Progress:_cleanup_main_win()
|
||||||
self.bufnr = nil
|
self.bufnr = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Progress:_cleanup_minimized_win()
|
||||||
|
if self.min_winid and vim.api.nvim_win_is_valid(self.min_winid) then
|
||||||
|
vim.api.nvim_win_close(self.min_winid, true)
|
||||||
|
end
|
||||||
|
self.min_winid = nil
|
||||||
|
self.min_bufnr = nil
|
||||||
|
end
|
||||||
|
|
||||||
function Progress:minimize()
|
function Progress:minimize()
|
||||||
if self.closing then
|
if self.closing then
|
||||||
return
|
return
|
||||||
|
|
@ -160,6 +189,7 @@ function Progress:minimize()
|
||||||
self.min_bufnr = bufnr
|
self.min_bufnr = bufnr
|
||||||
self.min_winid = winid
|
self.min_winid = winid
|
||||||
self:_render()
|
self:_render()
|
||||||
|
vim.notify_once("Restore progress window with :Oil --progress")
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param action oil.Action
|
---@param action oil.Action
|
||||||
|
|
@ -187,11 +217,7 @@ function Progress:close()
|
||||||
self.timer = nil
|
self.timer = nil
|
||||||
end
|
end
|
||||||
self:_cleanup_main_win()
|
self:_cleanup_main_win()
|
||||||
if self.min_winid and vim.api.nvim_win_is_valid(self.min_winid) then
|
self:_cleanup_minimized_win()
|
||||||
vim.api.nvim_win_close(self.min_winid, true)
|
|
||||||
end
|
|
||||||
self.min_winid = nil
|
|
||||||
self.min_bufnr = nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return Progress
|
return Progress
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ local Trie = {}
|
||||||
|
|
||||||
---@return oil.Trie
|
---@return oil.Trie
|
||||||
Trie.new = function()
|
Trie.new = function()
|
||||||
|
---@type oil.Trie
|
||||||
return setmetatable({
|
return setmetatable({
|
||||||
root = { values = {}, children = {} },
|
root = { values = {}, children = {} },
|
||||||
}, {
|
}, {
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
})
|
})
|
||||||
|
|
|
||||||
449
lua/oil/util.lua
449
lua/oil/util.lua
|
|
@ -8,6 +8,8 @@ local FIELD_NAME = constants.FIELD_NAME
|
||||||
local FIELD_TYPE = constants.FIELD_TYPE
|
local FIELD_TYPE = constants.FIELD_TYPE
|
||||||
local FIELD_META = constants.FIELD_META
|
local FIELD_META = constants.FIELD_META
|
||||||
|
|
||||||
|
---@alias oil.IconProvider fun(type: string, name: string, conf: table?): (icon: string, hl: string)
|
||||||
|
|
||||||
---@param url string
|
---@param url string
|
||||||
---@return nil|string
|
---@return nil|string
|
||||||
---@return nil|string
|
---@return nil|string
|
||||||
|
|
@ -19,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
|
||||||
|
|
@ -72,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
|
||||||
|
|
||||||
|
|
@ -155,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
|
||||||
|
|
@ -193,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
|
||||||
|
|
@ -281,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
|
||||||
|
|
@ -299,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 }[]
|
||||||
|
|
@ -311,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
|
||||||
|
|
@ -332,15 +366,27 @@ 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
|
||||||
|
|
||||||
---@param path string
|
---@param path string
|
||||||
|
---@param os_slash? boolean use os filesystem slash instead of posix slash
|
||||||
---@return string
|
---@return string
|
||||||
M.addslash = function(path)
|
M.addslash = function(path, os_slash)
|
||||||
if not vim.endswith(path, "/") then
|
local slash = "/"
|
||||||
return path .. "/"
|
if os_slash and require("oil.fs").is_windows then
|
||||||
|
slash = "\\"
|
||||||
|
end
|
||||||
|
|
||||||
|
local endslash = path:match(slash .. "$")
|
||||||
|
if not endslash then
|
||||||
|
return path .. slash
|
||||||
else
|
else
|
||||||
return path
|
return path
|
||||||
end
|
end
|
||||||
|
|
@ -352,6 +398,26 @@ M.is_floating_win = function(winid)
|
||||||
return vim.api.nvim_win_get_config(winid or 0).relative ~= ""
|
return vim.api.nvim_win_get_config(winid or 0).relative ~= ""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---Recalculate the window title for the current buffer
|
||||||
|
---@param winid nil|integer
|
||||||
|
---@return string
|
||||||
|
M.get_title = function(winid)
|
||||||
|
if config.float.get_win_title ~= nil then
|
||||||
|
return config.float.get_win_title(winid or 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
local src_buf = vim.api.nvim_win_get_buf(winid or 0)
|
||||||
|
local title = vim.api.nvim_buf_get_name(src_buf)
|
||||||
|
local scheme, path = M.parse_url(title)
|
||||||
|
|
||||||
|
if config.adapters[scheme] == "files" then
|
||||||
|
assert(path)
|
||||||
|
local fs = require("oil.fs")
|
||||||
|
title = vim.fn.fnamemodify(fs.posix_to_os_path(path), ":~")
|
||||||
|
end
|
||||||
|
return title
|
||||||
|
end
|
||||||
|
|
||||||
local winid_map = {}
|
local winid_map = {}
|
||||||
M.add_title_to_win = function(winid, opts)
|
M.add_title_to_win = function(winid, opts)
|
||||||
opts = opts or {}
|
opts = opts or {}
|
||||||
|
|
@ -359,21 +425,10 @@ M.add_title_to_win = function(winid, opts)
|
||||||
if not vim.api.nvim_win_is_valid(winid) then
|
if not vim.api.nvim_win_is_valid(winid) then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local function get_title()
|
|
||||||
local src_buf = vim.api.nvim_win_get_buf(winid)
|
|
||||||
local title = vim.api.nvim_buf_get_name(src_buf)
|
|
||||||
local scheme, path = M.parse_url(title)
|
|
||||||
if config.adapters[scheme] == "files" then
|
|
||||||
assert(path)
|
|
||||||
local fs = require("oil.fs")
|
|
||||||
title = vim.fn.fnamemodify(fs.posix_to_os_path(path), ":~")
|
|
||||||
end
|
|
||||||
return title
|
|
||||||
end
|
|
||||||
-- HACK to force the parent window to position itself
|
-- HACK to force the parent window to position itself
|
||||||
-- See https://github.com/neovim/neovim/issues/13403
|
-- See https://github.com/neovim/neovim/issues/13403
|
||||||
vim.cmd.redraw()
|
vim.cmd.redraw()
|
||||||
local title = get_title()
|
local title = M.get_title(winid)
|
||||||
local width = math.min(vim.api.nvim_win_get_width(winid) - 4, 2 + vim.api.nvim_strwidth(title))
|
local width = math.min(vim.api.nvim_win_get_width(winid) - 4, 2 + vim.api.nvim_strwidth(title))
|
||||||
local title_winid = winid_map[winid]
|
local title_winid = winid_map[winid]
|
||||||
local bufnr
|
local bufnr
|
||||||
|
|
@ -421,7 +476,7 @@ M.add_title_to_win = function(winid, opts)
|
||||||
if vim.api.nvim_win_get_buf(winid) ~= winbuf then
|
if vim.api.nvim_win_get_buf(winid) ~= winbuf then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local new_title = get_title()
|
local new_title = M.get_title(winid)
|
||||||
local new_width =
|
local new_width =
|
||||||
math.min(vim.api.nvim_win_get_width(winid) - 4, 2 + vim.api.nvim_strwidth(new_title))
|
math.min(vim.api.nvim_win_get_width(winid) - 4, 2 + vim.api.nvim_strwidth(new_title))
|
||||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, { " " .. new_title .. " " })
|
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, { " " .. new_title .. " " })
|
||||||
|
|
@ -470,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
|
||||||
|
|
@ -594,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
|
||||||
|
|
@ -626,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]
|
||||||
|
|
@ -650,15 +702,36 @@ 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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@return fun() restore Function that restores the cursor
|
||||||
|
M.hide_cursor = function()
|
||||||
|
vim.api.nvim_set_hl(0, "OilPreviewCursor", { nocombine = true, blend = 100 })
|
||||||
|
local original_guicursor = vim.go.guicursor
|
||||||
|
vim.go.guicursor = "a:OilPreviewCursor/OilPreviewCursor"
|
||||||
|
|
||||||
|
return function()
|
||||||
|
-- HACK: see https://github.com/neovim/neovim/issues/21018
|
||||||
|
vim.go.guicursor = "a:"
|
||||||
|
vim.cmd.redrawstatus()
|
||||||
|
vim.go.guicursor = original_guicursor
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
---@param preferred_win nil|integer
|
---@param preferred_win nil|integer
|
||||||
---@return nil|integer
|
---@return nil|integer
|
||||||
|
|
@ -714,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 = {}
|
||||||
|
|
@ -728,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,
|
||||||
|
|
@ -747,21 +821,27 @@ 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
|
||||||
|
|
||||||
|
---@return boolean
|
||||||
|
M.is_visual_mode = function()
|
||||||
|
local mode = vim.api.nvim_get_mode().mode
|
||||||
|
return mode:match("^[vV]") ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
---Get the current visual selection range. If not in visual mode, return nil.
|
---Get the current visual selection range. If not in visual mode, return nil.
|
||||||
---@return {start_lnum: integer, end_lnum: integer}?
|
---@return {start_lnum: integer, end_lnum: integer}?
|
||||||
M.get_visual_range = function()
|
M.get_visual_range = function()
|
||||||
local mode = vim.api.nvim_get_mode().mode
|
if not M.is_visual_mode() then
|
||||||
local is_visual = mode:match("^[vV]")
|
|
||||||
if not is_visual then
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
-- This is the best way to get the visual selection at the moment
|
-- This is the best way to get the visual selection at the moment
|
||||||
|
|
@ -774,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)
|
||||||
|
|
@ -783,17 +876,163 @@ M.run_after_load = function(bufnr, callback)
|
||||||
if vim.b[bufnr].oil_ready then
|
if vim.b[bufnr].oil_ready then
|
||||||
callback()
|
callback()
|
||||||
else
|
else
|
||||||
local autocmd_id
|
vim.api.nvim_create_autocmd("User", {
|
||||||
autocmd_id = vim.api.nvim_create_autocmd("User", {
|
|
||||||
pattern = "OilEnter",
|
pattern = "OilEnter",
|
||||||
callback = function(args)
|
callback = function(args)
|
||||||
if args.data.buf == bufnr then
|
if args.data.buf == bufnr then
|
||||||
callback()
|
vim.api.nvim_buf_call(bufnr, callback)
|
||||||
vim.api.nvim_del_autocmd(autocmd_id)
|
return true
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param entry oil.Entry
|
||||||
|
---@return boolean
|
||||||
|
M.is_directory = function(entry)
|
||||||
|
local is_directory = entry.type == "directory"
|
||||||
|
or (
|
||||||
|
entry.type == "link"
|
||||||
|
and entry.meta
|
||||||
|
and entry.meta.link_stat
|
||||||
|
and entry.meta.link_stat.type == "directory"
|
||||||
|
)
|
||||||
|
return is_directory == true
|
||||||
|
end
|
||||||
|
|
||||||
|
---Get the :edit path for an entry
|
||||||
|
---@param bufnr integer The oil buffer that contains the entry
|
||||||
|
---@param entry oil.Entry
|
||||||
|
---@param callback fun(normalized_url: string)
|
||||||
|
M.get_edit_path = function(bufnr, entry, callback)
|
||||||
|
local pathutil = require("oil.pathutil")
|
||||||
|
|
||||||
|
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
||||||
|
local scheme, dir = M.parse_url(bufname)
|
||||||
|
local adapter = M.get_adapter(bufnr, true)
|
||||||
|
assert(scheme and dir and adapter)
|
||||||
|
|
||||||
|
local url = scheme .. dir .. entry.name
|
||||||
|
if M.is_directory(entry) then
|
||||||
|
url = url .. "/"
|
||||||
|
end
|
||||||
|
|
||||||
|
if entry.name == ".." then
|
||||||
|
callback(scheme .. pathutil.parent(dir))
|
||||||
|
elseif adapter.get_entry_path then
|
||||||
|
adapter.get_entry_path(url, entry, callback)
|
||||||
|
else
|
||||||
|
adapter.normalize_url(url, callback)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Check for an icon provider and return a common icon provider API
|
||||||
|
---@return (oil.IconProvider)?
|
||||||
|
M.get_icon_provider = function()
|
||||||
|
-- prefer mini.icons
|
||||||
|
local _, mini_icons = pcall(require, "mini.icons")
|
||||||
|
---@diagnostic disable-next-line: undefined-field
|
||||||
|
if _G.MiniIcons then -- `_G.MiniIcons` is a better check to see if the module is setup
|
||||||
|
return function(type, name)
|
||||||
|
return mini_icons.get(type == "directory" and "directory" or "file", name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- fallback to `nvim-web-devicons`
|
||||||
|
local has_devicons, devicons = pcall(require, "nvim-web-devicons")
|
||||||
|
if has_devicons then
|
||||||
|
return function(type, name, conf)
|
||||||
|
if type == "directory" then
|
||||||
|
return conf and conf.directory or "", "OilDirIcon"
|
||||||
|
else
|
||||||
|
local icon, hl = devicons.get_icon(name)
|
||||||
|
icon = icon or (conf and conf.default_file or "")
|
||||||
|
return icon, hl
|
||||||
|
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
|
||||||
|
|
|
||||||
313
lua/oil/view.lua
313
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 (not config.view_options.is_hidden_file(name, bufnr) or config.view_options.show_hidden)
|
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
|
||||||
|
|
@ -79,7 +85,7 @@ M.toggle_hidden = function()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param is_hidden_file fun(filename: string, bufnr: nil|integer): boolean
|
---@param is_hidden_file fun(filename: string, bufnr: integer): boolean
|
||||||
M.set_is_hidden_file = function(is_hidden_file)
|
M.set_is_hidden_file = function(is_hidden_file)
|
||||||
local any_modified = are_any_modified()
|
local any_modified = are_any_modified()
|
||||||
if any_modified then
|
if any_modified then
|
||||||
|
|
@ -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
|
||||||
|
|
@ -212,7 +227,12 @@ end
|
||||||
---Delete unmodified, hidden oil buffers and if none remain, clear the cache
|
---Delete unmodified, hidden oil buffers and if none remain, clear the cache
|
||||||
M.delete_hidden_buffers = function()
|
M.delete_hidden_buffers = function()
|
||||||
local visible_buffers, hidden_buffers = get_visible_hidden_buffers()
|
local visible_buffers, hidden_buffers = get_visible_hidden_buffers()
|
||||||
if not visible_buffers or not hidden_buffers or not vim.tbl_isempty(visible_buffers) then
|
if
|
||||||
|
not visible_buffers
|
||||||
|
or not hidden_buffers
|
||||||
|
or not vim.tbl_isempty(visible_buffers)
|
||||||
|
or vim.fn.win_gettype() == "command"
|
||||||
|
then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
for _, bufnr in ipairs(hidden_buffers) do
|
for _, bufnr in ipairs(hidden_buffers) do
|
||||||
|
|
@ -237,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
|
||||||
|
|
@ -277,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
|
||||||
|
|
@ -326,9 +376,10 @@ M.initialize = function(bufnr)
|
||||||
vim.b[bufnr].EditorConfig_disable = 1
|
vim.b[bufnr].EditorConfig_disable = 1
|
||||||
session[bufnr] = session[bufnr] or {}
|
session[bufnr] = session[bufnr] or {}
|
||||||
for k, v in pairs(config.buf_options) do
|
for k, v in pairs(config.buf_options) do
|
||||||
vim.api.nvim_buf_set_option(bufnr, k, v)
|
vim.bo[bufnr][k] = v
|
||||||
end
|
end
|
||||||
M.set_win_options()
|
vim.api.nvim_buf_call(bufnr, M.set_win_options)
|
||||||
|
|
||||||
vim.api.nvim_create_autocmd("BufHidden", {
|
vim.api.nvim_create_autocmd("BufHidden", {
|
||||||
desc = "Delete oil buffers when no longer in use",
|
desc = "Delete oil buffers when no longer in use",
|
||||||
group = "Oil",
|
group = "Oil",
|
||||||
|
|
@ -386,10 +437,10 @@ 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", {
|
vim.api.nvim_create_autocmd({ "CursorMoved", "ModeChanged" }, {
|
||||||
desc = "Update oil preview window",
|
desc = "Update oil preview window",
|
||||||
group = "Oil",
|
group = "Oil",
|
||||||
buffer = bufnr,
|
buffer = bufnr,
|
||||||
|
|
@ -399,15 +450,15 @@ M.initialize = function(bufnr)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
constrain_cursor()
|
constrain_cursor(bufnr, config.constrain_cursor)
|
||||||
|
|
||||||
if config.preview.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
|
||||||
if timer then
|
if timer then
|
||||||
timer:again()
|
timer:again()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
timer = vim.loop.new_timer()
|
timer = uv.new_timer()
|
||||||
if not timer then
|
if not timer then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
@ -420,11 +471,13 @@ M.initialize = function(bufnr)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local entry = oil.get_cursor_entry()
|
local entry = oil.get_cursor_entry()
|
||||||
if entry then
|
-- Don't update in visual mode. Visual mode implies editing not browsing,
|
||||||
|
-- and updating the preview can cause flicker and stutter.
|
||||||
|
if entry and not util.is_visual_mode() then
|
||||||
local winid = util.get_preview_win()
|
local winid = util.get_preview_win()
|
||||||
if winid then
|
if winid then
|
||||||
if entry.id ~= vim.w[winid].oil_entry_id then
|
if entry.id ~= vim.w[winid].oil_entry_id then
|
||||||
oil.select({ preview = true })
|
oil.open_preview()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -434,13 +487,13 @@ 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
|
||||||
adapter
|
adapter
|
||||||
and adapter.name == "files"
|
and adapter.name == "files"
|
||||||
and config.experimental_watch_for_changes
|
and config.watch_for_changes
|
||||||
and not session[bufnr].fs_event
|
and not session[bufnr].fs_event
|
||||||
then
|
then
|
||||||
local fs_event = assert(uv.new_fs_event())
|
local fs_event = assert(uv.new_fs_event())
|
||||||
|
|
@ -450,6 +503,14 @@ M.initialize = function(bufnr)
|
||||||
assert(dir),
|
assert(dir),
|
||||||
{},
|
{},
|
||||||
vim.schedule_wrap(function(err, filename, events)
|
vim.schedule_wrap(function(err, filename, events)
|
||||||
|
if not vim.api.nvim_buf_is_valid(bufnr) then
|
||||||
|
local sess = session[bufnr]
|
||||||
|
if sess then
|
||||||
|
sess.fs_event = nil
|
||||||
|
end
|
||||||
|
fs_event:stop()
|
||||||
|
return
|
||||||
|
end
|
||||||
local mutator = require("oil.mutator")
|
local mutator = require("oil.mutator")
|
||||||
if err or vim.bo[bufnr].modified or vim.b[bufnr].oil_dirty or mutator.is_mutating() then
|
if err or vim.bo[bufnr].modified or vim.b[bufnr].oil_dirty or mutator.is_mutating() then
|
||||||
return
|
return
|
||||||
|
|
@ -516,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
|
||||||
|
|
||||||
|
|
@ -539,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(
|
||||||
|
|
@ -550,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
|
||||||
|
|
@ -583,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
|
||||||
|
|
@ -601,32 +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 not M.should_display(entry[FIELD_NAME], bufnr) then
|
local should_display, is_hidden = M.should_display(entry[FIELD_NAME], bufnr)
|
||||||
goto continue
|
if should_display then
|
||||||
end
|
local cols = M.format_entry_cols(entry, column_defs, col_width, adapter, is_hidden, bufnr)
|
||||||
local cols = M.format_entry_cols(entry, column_defs, col_width, adapter)
|
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
|
||||||
::continue::
|
|
||||||
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)
|
||||||
|
|
@ -639,19 +708,23 @@ 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)
|
||||||
if id then
|
if id then
|
||||||
local entry = cache.get_entry_by_id(id)
|
local entry = cache.get_entry_by_id(id)
|
||||||
if entry then
|
if entry then
|
||||||
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
|
||||||
|
|
||||||
|
constrain_cursor(bufnr, "name")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
@ -659,18 +732,48 @@ local function render_buffer(bufnr, opts)
|
||||||
return seek_after_render_found
|
return seek_after_render_found
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param name string
|
||||||
|
---@param meta? table
|
||||||
|
---@return string filename
|
||||||
|
---@return string|nil link_target
|
||||||
|
local function get_link_text(name, meta)
|
||||||
|
local link_text
|
||||||
|
if meta then
|
||||||
|
if meta.link_stat and meta.link_stat.type == "directory" then
|
||||||
|
name = name .. "/"
|
||||||
|
end
|
||||||
|
|
||||||
|
if meta.link then
|
||||||
|
link_text = "-> " .. meta.link
|
||||||
|
if meta.link_stat and meta.link_stat.type == "directory" then
|
||||||
|
link_text = util.addslash(link_text)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return name, link_text
|
||||||
|
end
|
||||||
|
|
||||||
---@private
|
---@private
|
||||||
---@param entry oil.InternalEntry
|
---@param entry oil.InternalEntry
|
||||||
---@param column_defs table[]
|
---@param column_defs table[]
|
||||||
---@param col_width integer[]
|
---@param col_width integer[]
|
||||||
---@param adapter oil.Adapter
|
---@param adapter oil.Adapter
|
||||||
|
---@param is_hidden boolean
|
||||||
|
---@param bufnr integer
|
||||||
---@return oil.TextChunk[]
|
---@return oil.TextChunk[]
|
||||||
M.format_entry_cols = function(entry, column_defs, col_width, adapter)
|
M.format_entry_cols = function(entry, column_defs, col_width, adapter, is_hidden, bufnr)
|
||||||
local name = entry[FIELD_NAME]
|
local name = entry[FIELD_NAME]
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
|
local hl_suffix = ""
|
||||||
|
if is_hidden then
|
||||||
|
hl_suffix = "Hidden"
|
||||||
|
end
|
||||||
if meta and meta.display_name then
|
if meta and meta.display_name then
|
||||||
name = meta.display_name
|
name = meta.display_name
|
||||||
end
|
end
|
||||||
|
-- We can't handle newlines in filenames (and shame on you for doing that)
|
||||||
|
name = name:gsub("\n", "")
|
||||||
-- First put the unique ID
|
-- First put the unique ID
|
||||||
local cols = {}
|
local cols = {}
|
||||||
local id_key = cache.format_id(entry[FIELD_ID])
|
local id_key = cache.format_id(entry[FIELD_ID])
|
||||||
|
|
@ -678,7 +781,7 @@ M.format_entry_cols = function(entry, column_defs, col_width, adapter)
|
||||||
table.insert(cols, id_key)
|
table.insert(cols, id_key)
|
||||||
-- Then add all the configured columns
|
-- Then add all the configured columns
|
||||||
for i, column in ipairs(column_defs) do
|
for i, column in ipairs(column_defs) do
|
||||||
local chunk = columns.render_col(adapter, column, entry)
|
local chunk = columns.render_col(adapter, column, entry, bufnr)
|
||||||
local text = type(chunk) == "table" and chunk[1] or chunk
|
local text = type(chunk) == "table" and chunk[1] or chunk
|
||||||
---@cast text string
|
---@cast text string
|
||||||
col_width[i + 1] = math.max(col_width[i + 1], vim.api.nvim_strwidth(text))
|
col_width[i + 1] = math.max(col_width[i + 1], vim.api.nvim_strwidth(text))
|
||||||
|
|
@ -686,32 +789,59 @@ M.format_entry_cols = function(entry, column_defs, col_width, adapter)
|
||||||
end
|
end
|
||||||
-- Always add the entry name at the end
|
-- Always add the entry name at the end
|
||||||
local entry_type = entry[FIELD_TYPE]
|
local entry_type = entry[FIELD_TYPE]
|
||||||
if entry_type == "directory" then
|
|
||||||
table.insert(cols, { name .. "/", "OilDir" })
|
local get_custom_hl = config.view_options.highlight_filename
|
||||||
elseif entry_type == "socket" then
|
local link_name, link_name_hl, link_target, link_target_hl
|
||||||
table.insert(cols, { name, "OilSocket" })
|
if get_custom_hl then
|
||||||
elseif entry_type == "link" then
|
local external_entry = util.export_entry(entry)
|
||||||
local link_text
|
|
||||||
if meta then
|
if entry_type == "link" then
|
||||||
if meta.link_stat and meta.link_stat.type == "directory" then
|
link_name, link_target = get_link_text(name, meta)
|
||||||
name = name .. "/"
|
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
|
end
|
||||||
|
|
||||||
if meta.link then
|
-- intentional fallthrough
|
||||||
link_text = "->" .. " " .. meta.link
|
else
|
||||||
if meta.link_stat and meta.link_stat.type == "directory" then
|
local hl = get_custom_hl(external_entry, is_hidden, false, false, bufnr)
|
||||||
link_text = util.addslash(link_text)
|
if hl then
|
||||||
|
-- Add the trailing / if this is a directory, this is important
|
||||||
|
if entry_type == "directory" then
|
||||||
|
name = name .. "/"
|
||||||
end
|
end
|
||||||
|
table.insert(cols, { name, hl })
|
||||||
|
return cols
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
table.insert(cols, { name, "OilLink" })
|
if entry_type == "directory" then
|
||||||
if link_text then
|
table.insert(cols, { name .. "/", "OilDir" .. hl_suffix })
|
||||||
table.insert(cols, { link_text, "OilLinkTarget" })
|
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
|
||||||
|
|
||||||
|
|
@ -730,6 +860,7 @@ local function get_used_columns()
|
||||||
return cols
|
return cols
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@type table<integer, fun(message: string)[]>
|
||||||
local pending_renders = {}
|
local pending_renders = {}
|
||||||
|
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
|
|
@ -765,7 +896,7 @@ M.render_buffer_async = function(bufnr, opts, callback)
|
||||||
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" })
|
||||||
util.render_text(bufnr, { "Error: " .. message })
|
util.render_text(bufnr, { "Error: " .. message })
|
||||||
if pending_renders[bufnr] then
|
if pending_renders[bufnr] then
|
||||||
for _, cb in ipairs(pending_renders) do
|
for _, cb in ipairs(pending_renders[bufnr]) do
|
||||||
cb(message)
|
cb(message)
|
||||||
end
|
end
|
||||||
pending_renders[bufnr] = nil
|
pending_renders[bufnr] = nil
|
||||||
|
|
@ -780,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
|
||||||
|
|
@ -799,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
|
||||||
|
|
@ -823,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
|
||||||
|
|
@ -839,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"
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,21 @@ import os
|
||||||
import os.path
|
import os.path
|
||||||
import re
|
import re
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import List
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
from nvim_doc_tools import (
|
from nvim_doc_tools import (
|
||||||
LuaParam,
|
LuaParam,
|
||||||
|
LuaTypes,
|
||||||
Vimdoc,
|
Vimdoc,
|
||||||
VimdocSection,
|
VimdocSection,
|
||||||
generate_md_toc,
|
generate_md_toc,
|
||||||
indent,
|
indent,
|
||||||
leftright,
|
leftright,
|
||||||
parse_functions,
|
parse_directory,
|
||||||
read_nvim_json,
|
read_nvim_json,
|
||||||
read_section,
|
read_section,
|
||||||
render_md_api,
|
render_md_api2,
|
||||||
render_vimdoc_api,
|
render_vimdoc_api2,
|
||||||
replace_section,
|
replace_section,
|
||||||
wrap,
|
wrap,
|
||||||
)
|
)
|
||||||
|
|
@ -37,8 +38,9 @@ def add_md_link_path(path: str, lines: List[str]) -> List[str]:
|
||||||
|
|
||||||
def update_md_api():
|
def update_md_api():
|
||||||
api_doc = os.path.join(DOC, "api.md")
|
api_doc = os.path.join(DOC, "api.md")
|
||||||
funcs = parse_functions(os.path.join(ROOT, "lua", "oil", "init.lua"))
|
types = parse_directory(os.path.join(ROOT, "lua"))
|
||||||
lines = ["\n"] + render_md_api(funcs, 2) + ["\n"]
|
funcs = types.files["oil/init.lua"].functions
|
||||||
|
lines = ["\n"] + render_md_api2(funcs, types, 2) + ["\n"]
|
||||||
replace_section(
|
replace_section(
|
||||||
api_doc,
|
api_doc,
|
||||||
r"^<!-- API -->$",
|
r"^<!-- API -->$",
|
||||||
|
|
@ -61,10 +63,25 @@ def update_md_api():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def update_readme_toc():
|
def update_readme():
|
||||||
toc = ["\n"] + generate_md_toc(README, max_level=1) + ["\n"]
|
def get_toc(filename: str) -> List[str]:
|
||||||
|
subtoc = generate_md_toc(os.path.join(DOC, filename))
|
||||||
|
return add_md_link_path("doc/" + filename, subtoc)
|
||||||
|
|
||||||
|
recipes_toc = get_toc("recipes.md")
|
||||||
|
|
||||||
replace_section(
|
replace_section(
|
||||||
README,
|
README,
|
||||||
|
r"^## Recipes$",
|
||||||
|
r"^#",
|
||||||
|
["\n"] + recipes_toc + ["\n"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def update_md_toc(filename: str, max_level: int = 99):
|
||||||
|
toc = ["\n"] + generate_md_toc(filename, max_level) + ["\n"]
|
||||||
|
replace_section(
|
||||||
|
filename,
|
||||||
r"^<!-- TOC -->$",
|
r"^<!-- TOC -->$",
|
||||||
r"^<!-- /TOC -->$",
|
r"^<!-- /TOC -->$",
|
||||||
toc,
|
toc,
|
||||||
|
|
@ -93,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 = [
|
||||||
|
|
@ -110,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(
|
||||||
|
|
@ -119,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",
|
||||||
|
|
@ -134,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 + [],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -225,21 +247,76 @@ def get_highlights_vimdoc() -> "VimdocSection":
|
||||||
return section
|
return section
|
||||||
|
|
||||||
|
|
||||||
|
def load_params(params: Dict[str, Any]) -> List[LuaParam]:
|
||||||
|
ret = []
|
||||||
|
for name, data in sorted(params.items()):
|
||||||
|
ret.append(LuaParam(name, data["type"], data["desc"]))
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
def get_actions_vimdoc() -> "VimdocSection":
|
def get_actions_vimdoc() -> "VimdocSection":
|
||||||
section = VimdocSection("Actions", "oil-actions", ["\n"])
|
section = VimdocSection("Actions", "oil-actions", ["\n"])
|
||||||
|
section.body.append(
|
||||||
|
"""The `keymaps` option in `oil.setup` allow you to create mappings using all the same parameters as |vim.keymap.set|.
|
||||||
|
>lua
|
||||||
|
keymaps = {
|
||||||
|
-- Mappings can be a string
|
||||||
|
["~"] = "<cmd>edit $HOME<CR>",
|
||||||
|
-- Mappings can be a function
|
||||||
|
["gd"] = function()
|
||||||
|
require("oil").set_columns({ "icon", "permissions", "size", "mtime" })
|
||||||
|
end,
|
||||||
|
-- You can pass additional opts to vim.keymap.set by using
|
||||||
|
-- a table with the mapping as the first element.
|
||||||
|
["<leader>ff"] = {
|
||||||
|
function()
|
||||||
|
require("telescope.builtin").find_files({
|
||||||
|
cwd = require("oil").get_current_dir()
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
mode = "n",
|
||||||
|
nowait = true,
|
||||||
|
desc = "Find files in the current directory"
|
||||||
|
},
|
||||||
|
-- Mappings that are a string starting with "actions." will be
|
||||||
|
-- one of the built-in actions, documented below.
|
||||||
|
["`"] = "actions.tcd",
|
||||||
|
-- Some actions have parameters. These are passed in via the `opts` key.
|
||||||
|
["<leader>:"] = {
|
||||||
|
"actions.open_cmdline",
|
||||||
|
opts = {
|
||||||
|
shorten_path = true,
|
||||||
|
modify = ":h",
|
||||||
|
},
|
||||||
|
desc = "Open the command line with the current directory as an argument",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
section.body.append("\n")
|
||||||
section.body.extend(
|
section.body.extend(
|
||||||
wrap(
|
wrap(
|
||||||
"""These are actions that can be used in the `keymaps` section of config options. You can also call them directly with `require("oil.actions").action_name.callback()`"""
|
"""Below are the actions that can be used in the `keymaps` section of config options. You can refer to them as strings (e.g. "actions.<action_name>") or you can use the functions directly with `require("oil.actions").action_name.callback()`"""
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
section.body.append("\n")
|
section.body.append("\n")
|
||||||
actions = read_nvim_json('require("oil.actions")._get_actions()')
|
actions = read_nvim_json('require("oil.actions")._get_actions()')
|
||||||
actions.sort(key=lambda a: a["name"])
|
actions.sort(key=lambda a: a["name"])
|
||||||
for action in actions:
|
for action in actions:
|
||||||
|
if action.get("deprecated"):
|
||||||
|
continue
|
||||||
name = action["name"]
|
name = action["name"]
|
||||||
desc = action["desc"]
|
desc = action["desc"]
|
||||||
section.body.append(leftright(name, f"*actions.{name}*"))
|
section.body.append(leftright(name, f"*actions.{name}*"))
|
||||||
section.body.extend(wrap(desc, 4))
|
section.body.extend(wrap(desc, 4))
|
||||||
|
params = action.get("parameters")
|
||||||
|
if params:
|
||||||
|
section.body.append("\n")
|
||||||
|
section.body.append(" Parameters:\n")
|
||||||
|
section.body.extend(
|
||||||
|
format_vimdoc_params(load_params(params), LuaTypes(), 6)
|
||||||
|
)
|
||||||
|
|
||||||
section.body.append("\n")
|
section.body.append("\n")
|
||||||
return section
|
return section
|
||||||
|
|
||||||
|
|
@ -264,7 +341,7 @@ def get_columns_vimdoc() -> "VimdocSection":
|
||||||
section.body.extend(wrap(col.summary, 4))
|
section.body.extend(wrap(col.summary, 4))
|
||||||
section.body.append("\n")
|
section.body.append("\n")
|
||||||
section.body.append(" Parameters:\n")
|
section.body.append(" Parameters:\n")
|
||||||
section.body.extend(format_vimdoc_params(col.params, 6))
|
section.body.extend(format_vimdoc_params(col.params, LuaTypes(), 6))
|
||||||
section.body.append("\n")
|
section.body.append("\n")
|
||||||
return section
|
return section
|
||||||
|
|
||||||
|
|
@ -279,13 +356,13 @@ 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.
|
||||||
https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html
|
https://specifications.freedesktop.org/trash-spec/1.0/
|
||||||
All features should work.
|
All features should work.
|
||||||
|
|
||||||
Mac:
|
Mac:
|
||||||
|
|
@ -294,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
|
||||||
|
|
@ -302,12 +379,13 @@ Windows:
|
||||||
|
|
||||||
def generate_vimdoc():
|
def generate_vimdoc():
|
||||||
doc = Vimdoc("oil.txt", "oil")
|
doc = Vimdoc("oil.txt", "oil")
|
||||||
funcs = parse_functions(os.path.join(ROOT, "lua", "oil", "init.lua"))
|
types = parse_directory(os.path.join(ROOT, "lua"))
|
||||||
|
funcs = types.files["oil/init.lua"].functions
|
||||||
doc.sections.extend(
|
doc.sections.extend(
|
||||||
[
|
[
|
||||||
get_options_vimdoc(),
|
get_options_vimdoc(),
|
||||||
get_options_detail_vimdoc(),
|
get_options_detail_vimdoc(),
|
||||||
VimdocSection("API", "oil-api", render_vimdoc_api("oil", funcs)),
|
VimdocSection("API", "oil-api", render_vimdoc_api2("oil", funcs, types)),
|
||||||
get_columns_vimdoc(),
|
get_columns_vimdoc(),
|
||||||
get_actions_vimdoc(),
|
get_actions_vimdoc(),
|
||||||
get_highlights_vimdoc(),
|
get_highlights_vimdoc(),
|
||||||
|
|
@ -323,5 +401,7 @@ def main() -> None:
|
||||||
"""Update the README"""
|
"""Update the README"""
|
||||||
update_config_options()
|
update_config_options()
|
||||||
update_md_api()
|
update_md_api()
|
||||||
update_readme_toc()
|
update_md_toc(README, max_level=1)
|
||||||
|
update_md_toc(os.path.join(DOC, "recipes.md"))
|
||||||
|
update_readme()
|
||||||
generate_vimdoc()
|
generate_vimdoc()
|
||||||
|
|
|
||||||
4
scripts/requirements.txt
Normal file
4
scripts/requirements.txt
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
pyparsing==3.0.9
|
||||||
|
black
|
||||||
|
isort
|
||||||
|
mypy
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ a.describe("Alternate buffer", function()
|
||||||
return oil.get_cursor_entry()
|
return oil.get_cursor_entry()
|
||||||
end, 10)
|
end, 10)
|
||||||
vim.api.nvim_win_set_cursor(0, { 1, 1 })
|
vim.api.nvim_win_set_cursor(0, { 1, 1 })
|
||||||
oil.select({ preview = true })
|
oil.open_preview()
|
||||||
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
||||||
assert.equals("foo", vim.fn.expand("#"))
|
assert.equals("foo", vim.fn.expand("#"))
|
||||||
end)
|
end)
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
28
tests/manual_progress.lua
Normal file
28
tests/manual_progress.lua
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
-- Manual test for minimizing/restoring progress window
|
||||||
|
local Progress = require("oil.mutator.progress")
|
||||||
|
|
||||||
|
local progress = Progress.new()
|
||||||
|
|
||||||
|
progress:show({
|
||||||
|
cancel = function()
|
||||||
|
progress:close()
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
for i = 1, 10, 1 do
|
||||||
|
vim.defer_fn(function()
|
||||||
|
progress:set_action({
|
||||||
|
type = "create",
|
||||||
|
url = string.format("oil:///tmp/test_%d.txt", i),
|
||||||
|
entry_type = "file",
|
||||||
|
}, i, 10)
|
||||||
|
end, (i - 1) * 1000)
|
||||||
|
end
|
||||||
|
|
||||||
|
vim.defer_fn(function()
|
||||||
|
progress:close()
|
||||||
|
end, 10000)
|
||||||
|
|
||||||
|
vim.keymap.set("n", "R", function()
|
||||||
|
progress:restore()
|
||||||
|
end, {})
|
||||||
|
|
@ -216,6 +216,26 @@ a.describe("mutator", function()
|
||||||
assert.are.same({ move1, move2 }, ordered_actions)
|
assert.are.same({ move1, move2 }, ordered_actions)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it("Handles a delete inside a moved folder", function()
|
||||||
|
-- delete in directory and move directory
|
||||||
|
-- DELETE /a/b.txt
|
||||||
|
-- MOVE /a/ -> /b/
|
||||||
|
local del = {
|
||||||
|
type = "delete",
|
||||||
|
url = "oil-test:///a/b.txt",
|
||||||
|
entry_type = "file",
|
||||||
|
}
|
||||||
|
local move = {
|
||||||
|
type = "move",
|
||||||
|
src_url = "oil-test:///a",
|
||||||
|
dest_url = "oil-test:///b",
|
||||||
|
entry_type = "directory",
|
||||||
|
}
|
||||||
|
local actions = { move, del }
|
||||||
|
local ordered_actions = mutator.enforce_action_order(actions)
|
||||||
|
assert.are.same({ del, move }, ordered_actions)
|
||||||
|
end)
|
||||||
|
|
||||||
it("Detects move directory loops", function()
|
it("Detects move directory loops", function()
|
||||||
local move = {
|
local move = {
|
||||||
type = "move",
|
type = "move",
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
require("plenary.async").tests.add_to_env()
|
require("plenary.async").tests.add_to_env()
|
||||||
local TmpDir = require("tests.tmpdir")
|
local TmpDir = require("tests.tmpdir")
|
||||||
|
local actions = require("oil.actions")
|
||||||
local oil = require("oil")
|
local oil = require("oil")
|
||||||
local test_util = require("tests.test_util")
|
local test_util = require("tests.test_util")
|
||||||
local view = require("oil.view")
|
local view = require("oil.view")
|
||||||
|
|
@ -131,4 +132,17 @@ a.describe("regression tests", function()
|
||||||
["baz.txt"] = "",
|
["baz.txt"] = "",
|
||||||
})
|
})
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
-- https://github.com/stevearc/oil.nvim/issues/355
|
||||||
|
a.it("can open files from floating window", function()
|
||||||
|
tmpdir:create({ "a.txt" })
|
||||||
|
a.util.scheduler()
|
||||||
|
oil.open_float(tmpdir.path)
|
||||||
|
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
||||||
|
actions.select.callback()
|
||||||
|
vim.wait(1000, function()
|
||||||
|
return vim.fn.expand("%:t") == "a.txt"
|
||||||
|
end, 10)
|
||||||
|
assert.equals("a.txt", vim.fn.expand("%:t"))
|
||||||
|
end)
|
||||||
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