mirror of
https://github.com/harivansh-afk/oil.nvim.git
synced 2026-04-16 06:02:45 +00:00
Compare commits
45 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 |
31 changed files with 1490 additions and 317 deletions
3
.github/workflows/tests.yml
vendored
3
.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
|
||||||
|
|
@ -52,8 +53,8 @@ jobs:
|
||||||
include:
|
include:
|
||||||
- nvim_tag: v0.8.3
|
- nvim_tag: v0.8.3
|
||||||
- nvim_tag: v0.9.4
|
- nvim_tag: v0.9.4
|
||||||
- nvim_tag: v0.10.0
|
|
||||||
- nvim_tag: v0.10.4
|
- nvim_tag: v0.10.4
|
||||||
|
- nvim_tag: v0.11.0
|
||||||
|
|
||||||
name: Run tests
|
name: Run tests
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
|
|
|
||||||
43
README.md
43
README.md
|
|
@ -12,6 +12,7 @@ https://user-images.githubusercontent.com/506791/209727111-6b4a11f4-634a-4efa-94
|
||||||
- [Options](#options)
|
- [Options](#options)
|
||||||
- [Adapters](#adapters)
|
- [Adapters](#adapters)
|
||||||
- [Recipes](#recipes)
|
- [Recipes](#recipes)
|
||||||
|
- [Third-party extensions](#third-party-extensions)
|
||||||
- [API](#api)
|
- [API](#api)
|
||||||
- [FAQ](#faq)
|
- [FAQ](#faq)
|
||||||
|
|
||||||
|
|
@ -21,7 +22,7 @@ https://user-images.githubusercontent.com/506791/209727111-6b4a11f4-634a-4efa-94
|
||||||
|
|
||||||
- Neovim 0.8+
|
- Neovim 0.8+
|
||||||
- Icon provider plugin (optional)
|
- Icon provider plugin (optional)
|
||||||
- [mini.icons](https://github.com/echasnovski/mini.nvim/blob/main/readmes/mini-icons.md) for file and folder icons
|
- [mini.icons](https://github.com/nvim-mini/mini.nvim/blob/main/readmes/mini-icons.md) for file and folder icons
|
||||||
- [nvim-web-devicons](https://github.com/nvim-tree/nvim-web-devicons) for file icons
|
- [nvim-web-devicons](https://github.com/nvim-tree/nvim-web-devicons) for file icons
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
@ -38,7 +39,7 @@ oil.nvim supports all the usual plugin managers
|
||||||
---@type oil.SetupOpts
|
---@type oil.SetupOpts
|
||||||
opts = {},
|
opts = {},
|
||||||
-- Optional dependencies
|
-- Optional dependencies
|
||||||
dependencies = { { "echasnovski/mini.icons", opts = {} } },
|
dependencies = { { "nvim-mini/mini.icons", opts = {} } },
|
||||||
-- dependencies = { "nvim-tree/nvim-web-devicons" }, -- use if you prefer nvim-web-devicons
|
-- dependencies = { "nvim-tree/nvim-web-devicons" }, -- use if you prefer nvim-web-devicons
|
||||||
-- Lazy loading is not recommended because it is very tricky to make it work correctly in all situations.
|
-- Lazy loading is not recommended because it is very tricky to make it work correctly in all situations.
|
||||||
lazy = false,
|
lazy = false,
|
||||||
|
|
@ -203,7 +204,7 @@ require("oil").setup({
|
||||||
["-"] = { "actions.parent", mode = "n" },
|
["-"] = { "actions.parent", mode = "n" },
|
||||||
["_"] = { "actions.open_cwd", mode = "n" },
|
["_"] = { "actions.open_cwd", mode = "n" },
|
||||||
["`"] = { "actions.cd", mode = "n" },
|
["`"] = { "actions.cd", mode = "n" },
|
||||||
["~"] = { "actions.cd", opts = { scope = "tab" }, mode = "n" },
|
["g~"] = { "actions.cd", opts = { scope = "tab" }, mode = "n" },
|
||||||
["gs"] = { "actions.change_sort", mode = "n" },
|
["gs"] = { "actions.change_sort", mode = "n" },
|
||||||
["gx"] = "actions.open_external",
|
["gx"] = "actions.open_external",
|
||||||
["g."] = { "actions.toggle_hidden", mode = "n" },
|
["g."] = { "actions.toggle_hidden", mode = "n" },
|
||||||
|
|
@ -241,6 +242,8 @@ require("oil").setup({
|
||||||
},
|
},
|
||||||
-- Extra arguments to pass to SCP when moving/copying files over SSH
|
-- Extra arguments to pass to SCP when moving/copying files over SSH
|
||||||
extra_scp_args = {},
|
extra_scp_args = {},
|
||||||
|
-- Extra arguments to pass to aws s3 when creating/deleting/moving/copying files using aws s3
|
||||||
|
extra_s3_args = {},
|
||||||
-- EXPERIMENTAL support for performing file operations with git
|
-- EXPERIMENTAL support for performing file operations with git
|
||||||
git = {
|
git = {
|
||||||
-- Return true to automatically git add/mv/rm files
|
-- Return true to automatically git add/mv/rm files
|
||||||
|
|
@ -261,7 +264,7 @@ require("oil").setup({
|
||||||
-- max_width and max_height can be integers or a float between 0 and 1 (e.g. 0.4 for 40%)
|
-- 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,
|
||||||
},
|
},
|
||||||
|
|
@ -306,7 +309,7 @@ require("oil").setup({
|
||||||
min_height = { 5, 0.1 },
|
min_height = { 5, 0.1 },
|
||||||
-- optionally define an integer/float for the exact height of the preview window
|
-- optionally define an integer/float for the exact height of the preview window
|
||||||
height = nil,
|
height = nil,
|
||||||
border = "rounded",
|
border = nil,
|
||||||
win_options = {
|
win_options = {
|
||||||
winblend = 0,
|
winblend = 0,
|
||||||
},
|
},
|
||||||
|
|
@ -319,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,
|
||||||
|
|
@ -327,11 +330,11 @@ require("oil").setup({
|
||||||
},
|
},
|
||||||
-- Configuration for the floating SSH window
|
-- Configuration for the floating SSH window
|
||||||
ssh = {
|
ssh = {
|
||||||
border = "rounded",
|
border = nil,
|
||||||
},
|
},
|
||||||
-- Configuration for the floating keymaps help window
|
-- Configuration for the floating keymaps help window
|
||||||
keymaps_help = {
|
keymaps_help = {
|
||||||
border = "rounded",
|
border = nil,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
@ -354,12 +357,30 @@ This may look familiar. In fact, this is the same url format that netrw uses.
|
||||||
|
|
||||||
Note that at the moment the ssh adapter does not support Windows machines, and it requires the server to have a `/bin/sh` binary as well as standard unix commands (`ls`, `rm`, `mv`, `mkdir`, `chmod`, `cp`, `touch`, `ln`, `echo`).
|
Note that at the moment the ssh adapter does not support Windows machines, and it requires the server to have a `/bin/sh` binary as well as standard unix commands (`ls`, `rm`, `mv`, `mkdir`, `chmod`, `cp`, `touch`, `ln`, `echo`).
|
||||||
|
|
||||||
|
### S3
|
||||||
|
|
||||||
|
This adapter allows you to browse files stored in aws s3. To use it, make sure `aws` is setup correctly and then simply open a buffer using the following name template:
|
||||||
|
|
||||||
|
```
|
||||||
|
nvim oil-s3://[bucket]/[path]
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that older versions of Neovim don't support numbers in the url, so for Neovim 0.11 and older the url starts with `oil-sss`.
|
||||||
|
|
||||||
## Recipes
|
## Recipes
|
||||||
|
|
||||||
- [Toggle file detail view](doc/recipes.md#toggle-file-detail-view)
|
- [Toggle file detail view](doc/recipes.md#toggle-file-detail-view)
|
||||||
- [Show CWD in the winbar](doc/recipes.md#show-cwd-in-the-winbar)
|
- [Show CWD in the winbar](doc/recipes.md#show-cwd-in-the-winbar)
|
||||||
- [Hide gitignored files and show git tracked hidden files](doc/recipes.md#hide-gitignored-files-and-show-git-tracked-hidden-files)
|
- [Hide gitignored files and show git tracked hidden files](doc/recipes.md#hide-gitignored-files-and-show-git-tracked-hidden-files)
|
||||||
|
|
||||||
|
## Third-party extensions
|
||||||
|
|
||||||
|
These are plugins maintained by other authors that extend the functionality of oil.nvim.
|
||||||
|
|
||||||
|
- [oil-git-status.nvim](https://github.com/refractalize/oil-git-status.nvim) - Shows git status of files in statuscolumn
|
||||||
|
- [oil-git.nvim](https://github.com/benomahony/oil-git.nvim) - Shows git status of files with colour and symbols
|
||||||
|
- [oil-lsp-diagnostics.nvim](https://github.com/JezerM/oil-lsp-diagnostics.nvim) - Shows LSP diagnostics indicator as virtual text
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
<!-- API -->
|
<!-- API -->
|
||||||
|
|
@ -373,7 +394,7 @@ Note that at the moment the ssh adapter does not support Windows machines, and i
|
||||||
- [toggle_hidden()](doc/api.md#toggle_hidden)
|
- [toggle_hidden()](doc/api.md#toggle_hidden)
|
||||||
- [get_current_dir(bufnr)](doc/api.md#get_current_dirbufnr)
|
- [get_current_dir(bufnr)](doc/api.md#get_current_dirbufnr)
|
||||||
- [open_float(dir, opts, cb)](doc/api.md#open_floatdir-opts-cb)
|
- [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, opts, cb)](doc/api.md#opendir-opts-cb)
|
- [open(dir, opts, cb)](doc/api.md#opendir-opts-cb)
|
||||||
- [close(opts)](doc/api.md#closeopts)
|
- [close(opts)](doc/api.md#closeopts)
|
||||||
- [open_preview(opts, callback)](doc/api.md#open_previewopts-callback)
|
- [open_preview(opts, callback)](doc/api.md#open_previewopts-callback)
|
||||||
|
|
@ -400,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
|
||||||
|
|
||||||
|
|
@ -416,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).
|
||||||
|
|
|
||||||
37
doc/api.md
37
doc/api.md
|
|
@ -11,7 +11,7 @@
|
||||||
- [toggle_hidden()](#toggle_hidden)
|
- [toggle_hidden()](#toggle_hidden)
|
||||||
- [get_current_dir(bufnr)](#get_current_dirbufnr)
|
- [get_current_dir(bufnr)](#get_current_dirbufnr)
|
||||||
- [open_float(dir, opts, cb)](#open_floatdir-opts-cb)
|
- [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, opts, cb)](#opendir-opts-cb)
|
- [open(dir, opts, cb)](#opendir-opts-cb)
|
||||||
- [close(opts)](#closeopts)
|
- [close(opts)](#closeopts)
|
||||||
- [open_preview(opts, callback)](#open_previewopts-callback)
|
- [open_preview(opts, callback)](#open_previewopts-callback)
|
||||||
|
|
@ -107,14 +107,20 @@ Open oil browser in a floating window
|
||||||
| >>split | `nil\|"aboveleft"\|"belowright"\|"topleft"\|"botright"` | Split modifier |
|
| >>split | `nil\|"aboveleft"\|"belowright"\|"topleft"\|"botright"` | Split modifier |
|
||||||
| cb | `nil\|fun()` | Called after the oil buffer is ready |
|
| 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, opts, cb)
|
## open(dir, opts, cb)
|
||||||
|
|
||||||
|
|
@ -159,15 +165,16 @@ Preview the entry under the cursor in a split
|
||||||
`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\|oil.SelectOpts` | |
|
| opts | `nil\|oil.SelectOpts` | |
|
||||||
| >vertical | `nil\|boolean` | Open the buffer in a vertical split |
|
| >vertical | `nil\|boolean` | Open the buffer in a vertical split |
|
||||||
| >horizontal | `nil\|boolean` | Open the buffer in a horizontal split |
|
| >horizontal | `nil\|boolean` | Open the buffer in a horizontal split |
|
||||||
| >split | `nil\|"aboveleft"\|"belowright"\|"topleft"\|"botright"` | Split modifier |
|
| >split | `nil\|"aboveleft"\|"belowright"\|"topleft"\|"botright"` | Split modifier |
|
||||||
| >tab | `nil\|boolean` | Open the buffer in a new tab |
|
| >tab | `nil\|boolean` | Open the buffer in a new tab |
|
||||||
| >close | `nil\|boolean` | Close the original oil buffer once selection is made |
|
| >close | `nil\|boolean` | Close the original oil buffer once selection is made |
|
||||||
| callback | `nil\|fun(err: nil\|string)` | Called once all entries have been opened |
|
| >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 |
|
||||||
|
|
||||||
## save(opts, cb)
|
## save(opts, cb)
|
||||||
|
|
||||||
|
|
|
||||||
62
doc/oil.txt
62
doc/oil.txt
|
|
@ -86,7 +86,7 @@ CONFIG *oil-confi
|
||||||
["-"] = { "actions.parent", mode = "n" },
|
["-"] = { "actions.parent", mode = "n" },
|
||||||
["_"] = { "actions.open_cwd", mode = "n" },
|
["_"] = { "actions.open_cwd", mode = "n" },
|
||||||
["`"] = { "actions.cd", mode = "n" },
|
["`"] = { "actions.cd", mode = "n" },
|
||||||
["~"] = { "actions.cd", opts = { scope = "tab" }, mode = "n" },
|
["g~"] = { "actions.cd", opts = { scope = "tab" }, mode = "n" },
|
||||||
["gs"] = { "actions.change_sort", mode = "n" },
|
["gs"] = { "actions.change_sort", mode = "n" },
|
||||||
["gx"] = "actions.open_external",
|
["gx"] = "actions.open_external",
|
||||||
["g."] = { "actions.toggle_hidden", mode = "n" },
|
["g."] = { "actions.toggle_hidden", mode = "n" },
|
||||||
|
|
@ -124,6 +124,8 @@ CONFIG *oil-confi
|
||||||
},
|
},
|
||||||
-- Extra arguments to pass to SCP when moving/copying files over SSH
|
-- Extra arguments to pass to SCP when moving/copying files over SSH
|
||||||
extra_scp_args = {},
|
extra_scp_args = {},
|
||||||
|
-- Extra arguments to pass to aws s3 when creating/deleting/moving/copying files using aws s3
|
||||||
|
extra_s3_args = {},
|
||||||
-- EXPERIMENTAL support for performing file operations with git
|
-- EXPERIMENTAL support for performing file operations with git
|
||||||
git = {
|
git = {
|
||||||
-- Return true to automatically git add/mv/rm files
|
-- Return true to automatically git add/mv/rm files
|
||||||
|
|
@ -144,7 +146,7 @@ CONFIG *oil-confi
|
||||||
-- max_width and max_height can be integers or a float between 0 and 1 (e.g. 0.4 for 40%)
|
-- 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,
|
||||||
},
|
},
|
||||||
|
|
@ -189,7 +191,7 @@ CONFIG *oil-confi
|
||||||
min_height = { 5, 0.1 },
|
min_height = { 5, 0.1 },
|
||||||
-- optionally define an integer/float for the exact height of the preview window
|
-- optionally define an integer/float for the exact height of the preview window
|
||||||
height = nil,
|
height = nil,
|
||||||
border = "rounded",
|
border = nil,
|
||||||
win_options = {
|
win_options = {
|
||||||
winblend = 0,
|
winblend = 0,
|
||||||
},
|
},
|
||||||
|
|
@ -202,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,
|
||||||
|
|
@ -210,11 +212,11 @@ CONFIG *oil-confi
|
||||||
},
|
},
|
||||||
-- Configuration for the floating SSH window
|
-- Configuration for the floating SSH window
|
||||||
ssh = {
|
ssh = {
|
||||||
border = "rounded",
|
border = nil,
|
||||||
},
|
},
|
||||||
-- Configuration for the floating keymaps help window
|
-- Configuration for the floating keymaps help window
|
||||||
keymaps_help = {
|
keymaps_help = {
|
||||||
border = "rounded",
|
border = nil,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
<
|
<
|
||||||
|
|
@ -319,12 +321,20 @@ open_float({dir}, {opts}, {cb}) *oil.open_floa
|
||||||
plit modifier
|
plit modifier
|
||||||
{cb} `nil|fun()` Called after the oil buffer is ready
|
{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}, {opts}, {cb}) *oil.open*
|
open({dir}, {opts}, {cb}) *oil.open*
|
||||||
Open oil browser for a directory
|
Open oil browser for a directory
|
||||||
|
|
@ -373,6 +383,10 @@ select({opts}, {callback}) *oil.selec
|
||||||
{tab} `nil|boolean` Open the buffer in a new tab
|
{tab} `nil|boolean` Open the buffer in a new tab
|
||||||
{close} `nil|boolean` Close the original oil buffer once
|
{close} `nil|boolean` Close the original oil buffer once
|
||||||
selection is made
|
selection is made
|
||||||
|
{handle_buffer_callback} `nil|fun(buf_id: integer)` If defined, all
|
||||||
|
other buffer related options here would be ignored. This
|
||||||
|
callback allows you to take over the process of opening
|
||||||
|
the buffer yourself.
|
||||||
{callback} `nil|fun(err: nil|string)` Called once all entries have been
|
{callback} `nil|fun(err: nil|string)` Called once all entries have been
|
||||||
opened
|
opened
|
||||||
|
|
||||||
|
|
@ -408,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*
|
||||||
|
|
@ -417,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
|
||||||
|
|
@ -424,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
|
||||||
|
|
@ -440,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
|
||||||
|
|
@ -449,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*
|
||||||
|
|
@ -459,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*
|
||||||
|
|
@ -469,16 +489,18 @@ atime *column-atim
|
||||||
Parameters:
|
Parameters:
|
||||||
{highlight} `string|fun(value: string): string` Highlight group, or
|
{highlight} `string|fun(value: string): string` Highlight group, or
|
||||||
function that returns a highlight group
|
function that returns a highlight group
|
||||||
|
{align} `"left"|"center"|"right"` Text alignment within the column
|
||||||
{format} `string` Format string (see :help strftime)
|
{format} `string` Format string (see :help strftime)
|
||||||
|
|
||||||
birthtime *column-birthtime*
|
birthtime *column-birthtime*
|
||||||
Adapters: files
|
Adapters: files, s3
|
||||||
Sortable: this column can be used in view_props.sort
|
Sortable: this column can be used in view_props.sort
|
||||||
The time the file was created
|
The time the file was created
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
{highlight} `string|fun(value: string): string` Highlight group, or
|
{highlight} `string|fun(value: string): string` Highlight group, or
|
||||||
function that returns a highlight group
|
function that returns a highlight group
|
||||||
|
{align} `"left"|"center"|"right"` Text alignment within the column
|
||||||
{format} `string` Format string (see :help strftime)
|
{format} `string` Format string (see :help strftime)
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
@ -545,6 +567,9 @@ close *actions.clos
|
||||||
Parameters:
|
Parameters:
|
||||||
{exit_if_last_buf} `boolean` Exit vim if oil is closed as the last buffer
|
{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
|
||||||
|
|
||||||
|
|
@ -565,6 +590,12 @@ open_terminal *actions.open_termina
|
||||||
parent *actions.parent*
|
parent *actions.parent*
|
||||||
Navigate to the parent path
|
Navigate to the parent path
|
||||||
|
|
||||||
|
paste_from_system_clipboard *actions.paste_from_system_clipboard*
|
||||||
|
Paste the system clipboard into the current oil directory
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
{delete_original} `boolean` Delete the original file after copying
|
||||||
|
|
||||||
preview *actions.preview*
|
preview *actions.preview*
|
||||||
Open the entry under the cursor in a preview window, or close the preview
|
Open the entry under the cursor in a preview window, or close the preview
|
||||||
window if already open
|
window if already open
|
||||||
|
|
@ -578,6 +609,12 @@ preview *actions.previe
|
||||||
preview_scroll_down *actions.preview_scroll_down*
|
preview_scroll_down *actions.preview_scroll_down*
|
||||||
Scroll down in the preview window
|
Scroll down in the preview window
|
||||||
|
|
||||||
|
preview_scroll_left *actions.preview_scroll_left*
|
||||||
|
Scroll left in the preview window
|
||||||
|
|
||||||
|
preview_scroll_right *actions.preview_scroll_right*
|
||||||
|
Scroll right in the preview window
|
||||||
|
|
||||||
preview_scroll_up *actions.preview_scroll_up*
|
preview_scroll_up *actions.preview_scroll_up*
|
||||||
Scroll up in the preview window
|
Scroll up in the preview window
|
||||||
|
|
||||||
|
|
@ -631,6 +668,9 @@ yank_entry *actions.yank_entr
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
HIGHLIGHTS *oil-highlights*
|
HIGHLIGHTS *oil-highlights*
|
||||||
|
|
||||||
|
OilEmpty *hl-OilEmpty*
|
||||||
|
Empty column values
|
||||||
|
|
||||||
OilHidden *hl-OilHidden*
|
OilHidden *hl-OilHidden*
|
||||||
Hidden entry in an oil buffer
|
Hidden entry in an oil buffer
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,30 @@ M.preview_scroll_up = {
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
M.preview_scroll_left = {
|
||||||
|
desc = "Scroll left in the preview window",
|
||||||
|
callback = function()
|
||||||
|
local winid = util.get_preview_win()
|
||||||
|
if winid then
|
||||||
|
vim.api.nvim_win_call(winid, function()
|
||||||
|
vim.cmd.normal({ "zH", bang = true })
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
M.preview_scroll_right = {
|
||||||
|
desc = "Scroll right in the preview window",
|
||||||
|
callback = function()
|
||||||
|
local winid = util.get_preview_win()
|
||||||
|
if winid then
|
||||||
|
vim.api.nvim_win_call(winid, function()
|
||||||
|
vim.cmd.normal({ "zL", bang = true })
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
M.parent = {
|
M.parent = {
|
||||||
desc = "Navigate to the parent path",
|
desc = "Navigate to the parent path",
|
||||||
callback = oil.open,
|
callback = oil.open,
|
||||||
|
|
@ -229,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
|
||||||
|
|
@ -418,6 +453,26 @@ M.copy_entry_filename = {
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
M.copy_to_system_clipboard = {
|
||||||
|
desc = "Copy the entry under the cursor to the system clipboard",
|
||||||
|
callback = function()
|
||||||
|
require("oil.clipboard").copy_to_system_clipboard()
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
M.paste_from_system_clipboard = {
|
||||||
|
desc = "Paste the system clipboard into the current oil directory",
|
||||||
|
callback = function(opts)
|
||||||
|
require("oil.clipboard").paste_from_system_clipboard(opts and opts.delete_original)
|
||||||
|
end,
|
||||||
|
parameters = {
|
||||||
|
delete_original = {
|
||||||
|
type = "boolean",
|
||||||
|
desc = "Delete the original file after copying",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
M.open_cmdline_dir = {
|
M.open_cmdline_dir = {
|
||||||
desc = "Open vim cmdline with current directory as an argument",
|
desc = "Open vim cmdline with current directory as an argument",
|
||||||
deprecated = true,
|
deprecated = true,
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ local fs = require("oil.fs")
|
||||||
local git = require("oil.git")
|
local git = require("oil.git")
|
||||||
local log = require("oil.log")
|
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
|
||||||
|
|
||||||
|
|
@ -183,7 +182,17 @@ for _, time_key in ipairs({ "ctime", "mtime", "atime", "birthtime" }) do
|
||||||
-- Replace placeholders with a pattern that matches non-space characters (e.g. %H -> %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
|
-- and whitespace with a pattern that matches any amount of whitespace
|
||||||
-- e.g. "%b %d %Y" -> "%S+%s+%S+%s+%S+"
|
-- e.g. "%b %d %Y" -> "%S+%s+%S+%s+%S+"
|
||||||
pattern = fmt:gsub("%%.", "%%S+"):gsub("%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
|
||||||
|
|
@ -267,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)
|
||||||
)
|
)
|
||||||
|
|
@ -530,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)
|
||||||
|
|
@ -610,20 +619,12 @@ M.perform_action = function(action, cb)
|
||||||
end
|
end
|
||||||
|
|
||||||
if config.delete_to_trash then
|
if config.delete_to_trash then
|
||||||
if config.trash_command then
|
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)
|
||||||
|
|
@ -641,7 +642,7 @@ M.perform_action = function(action, cb)
|
||||||
cb("files adapter doesn't support cross-adapter move")
|
cb("files adapter doesn't support cross-adapter move")
|
||||||
end
|
end
|
||||||
elseif action.type == "copy" then
|
elseif action.type == "copy" then
|
||||||
local dest_adapter = config.get_adapter_by_scheme(action.dest_url)
|
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
||||||
if dest_adapter == M then
|
if dest_adapter == M then
|
||||||
local _, src_path = util.parse_url(action.src_url)
|
local _, src_path = util.parse_url(action.src_url)
|
||||||
assert(src_path)
|
assert(src_path)
|
||||||
|
|
|
||||||
|
|
@ -1,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
|
||||||
|
|
@ -303,8 +303,8 @@ M.perform_action = function(action, cb)
|
||||||
local conn = get_connection(action.url)
|
local conn = get_connection(action.url)
|
||||||
conn:rm(res.path, cb)
|
conn:rm(res.path, cb)
|
||||||
elseif action.type == "move" then
|
elseif action.type == "move" then
|
||||||
local src_adapter = config.get_adapter_by_scheme(action.src_url)
|
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
||||||
local dest_adapter = config.get_adapter_by_scheme(action.dest_url)
|
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
||||||
if src_adapter == M and dest_adapter == M then
|
if src_adapter == M and dest_adapter == M then
|
||||||
local src_res = M.parse_url(action.src_url)
|
local src_res = M.parse_url(action.src_url)
|
||||||
local dest_res = M.parse_url(action.dest_url)
|
local dest_res = M.parse_url(action.dest_url)
|
||||||
|
|
@ -324,8 +324,8 @@ M.perform_action = function(action, cb)
|
||||||
cb("We should never attempt to move across adapters")
|
cb("We should never attempt to move across adapters")
|
||||||
end
|
end
|
||||||
elseif action.type == "copy" then
|
elseif action.type == "copy" then
|
||||||
local src_adapter = config.get_adapter_by_scheme(action.src_url)
|
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
||||||
local dest_adapter = config.get_adapter_by_scheme(action.dest_url)
|
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
||||||
if src_adapter == M and dest_adapter == M then
|
if src_adapter == M and dest_adapter == M then
|
||||||
local src_res = M.parse_url(action.src_url)
|
local src_res = M.parse_url(action.src_url)
|
||||||
local dest_res = M.parse_url(action.dest_url)
|
local dest_res = M.parse_url(action.dest_url)
|
||||||
|
|
|
||||||
|
|
@ -42,10 +42,17 @@ local function parse_ls_line(line)
|
||||||
local name, size, date, major, minor
|
local name, size, date, major, minor
|
||||||
if typechar == "c" or typechar == "b" then
|
if typechar == "c" or typechar == "b" then
|
||||||
major, minor, date, name = rem:match("^(%d+)%s*,%s*(%d+)%s+(%S+%s+%d+%s+%d%d:?%d%d)%s+(.*)")
|
major, minor, date, name = rem:match("^(%d+)%s*,%s*(%d+)%s+(%S+%s+%d+%s+%d%d:?%d%d)%s+(.*)")
|
||||||
|
if name == nil then
|
||||||
|
major, minor, date, name =
|
||||||
|
rem:match("^(%d+)%s*,%s*(%d+)%s+(%d+%-%d+%-%d+%s+%d%d:?%d%d)%s+(.*)")
|
||||||
|
end
|
||||||
meta.major = tonumber(major)
|
meta.major = tonumber(major)
|
||||||
meta.minor = tonumber(minor)
|
meta.minor = tonumber(minor)
|
||||||
else
|
else
|
||||||
size, date, name = rem:match("^(%d+)%s+(%S+%s+%d+%s+%d%d:?%d%d)%s+(.*)")
|
size, date, name = rem:match("^(%d+)%s+(%S+%s+%d+%s+%d%d:?%d%d)%s+(.*)")
|
||||||
|
if name == nil then
|
||||||
|
size, date, name = rem:match("^(%d+)%s+(%d+%-%d+%-%d+%s+%d%d:?%d%d)%s+(.*)")
|
||||||
|
end
|
||||||
meta.size = tonumber(size)
|
meta.size = tonumber(size)
|
||||||
end
|
end
|
||||||
meta.iso_modified_date = date
|
meta.iso_modified_date = date
|
||||||
|
|
|
||||||
|
|
@ -447,7 +447,7 @@ M.render_action = function(action)
|
||||||
local entry = assert(cache.get_entry_by_url(action.url))
|
local entry = assert(cache.get_entry_by_url(action.url))
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
---@type oil.TrashInfo
|
---@type oil.TrashInfo
|
||||||
local trash_info = meta and meta.trash_info
|
local trash_info = assert(meta).trash_info
|
||||||
local short_path = fs.shorten_path(trash_info.original_path)
|
local short_path = fs.shorten_path(trash_info.original_path)
|
||||||
return string.format(" PURGE %s", short_path)
|
return string.format(" PURGE %s", short_path)
|
||||||
elseif action.type == "move" then
|
elseif action.type == "move" then
|
||||||
|
|
@ -561,7 +561,7 @@ M.perform_action = function(action, cb)
|
||||||
local entry = assert(cache.get_entry_by_url(action.url))
|
local entry = assert(cache.get_entry_by_url(action.url))
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
---@type oil.TrashInfo
|
---@type oil.TrashInfo
|
||||||
local trash_info = meta and meta.trash_info
|
local trash_info = assert(meta).trash_info
|
||||||
purge(trash_info, cb)
|
purge(trash_info, cb)
|
||||||
elseif action.type == "move" then
|
elseif action.type == "move" then
|
||||||
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
||||||
|
|
@ -576,7 +576,7 @@ M.perform_action = function(action, cb)
|
||||||
local entry = assert(cache.get_entry_by_url(action.src_url))
|
local entry = assert(cache.get_entry_by_url(action.src_url))
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
---@type oil.TrashInfo
|
---@type oil.TrashInfo
|
||||||
local trash_info = meta and 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)
|
||||||
|
|
@ -607,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 and 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")
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
@ -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 and 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
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,16 @@ end
|
||||||
|
|
||||||
---@param init_command? string
|
---@param init_command? string
|
||||||
function PowershellConnection:_init(init_command)
|
function PowershellConnection:_init(init_command)
|
||||||
|
-- For some reason beyond my understanding, at least one of the following
|
||||||
|
-- things requires `noshellslash` to avoid the embeded powershell process to
|
||||||
|
-- send only "" to the stdout (never calling the callback because
|
||||||
|
-- "===DONE(True)===" is never sent to stdout)
|
||||||
|
-- * vim.fn.jobstart
|
||||||
|
-- * cmd.exe
|
||||||
|
-- * powershell.exe
|
||||||
|
local saved_shellslash = vim.o.shellslash
|
||||||
|
vim.o.shellslash = false
|
||||||
|
|
||||||
-- 65001 is the UTF-8 codepage
|
-- 65001 is the UTF-8 codepage
|
||||||
-- powershell needs to be launched with the UTF-8 codepage to use it for both stdin and stdout
|
-- powershell needs to be launched with the UTF-8 codepage to use it for both stdin and stdout
|
||||||
local jid = vim.fn.jobstart({
|
local jid = vim.fn.jobstart({
|
||||||
|
|
@ -57,6 +67,7 @@ function PowershellConnection:_init(init_command)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
vim.o.shellslash = saved_shellslash
|
||||||
|
|
||||||
if jid == 0 then
|
if jid == 0 then
|
||||||
self:_set_error("passed invalid arguments to 'powershell'")
|
self:_set_error("passed invalid arguments to 'powershell'")
|
||||||
|
|
|
||||||
370
lua/oil/clipboard.lua
Normal file
370
lua/oil/clipboard.lua
Normal file
|
|
@ -0,0 +1,370 @@
|
||||||
|
local cache = require("oil.cache")
|
||||||
|
local columns = require("oil.columns")
|
||||||
|
local config = require("oil.config")
|
||||||
|
local fs = require("oil.fs")
|
||||||
|
local oil = require("oil")
|
||||||
|
local parser = require("oil.mutator.parser")
|
||||||
|
local util = require("oil.util")
|
||||||
|
local view = require("oil.view")
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
---@return "wayland"|"x11"|nil
|
||||||
|
local function get_linux_session_type()
|
||||||
|
local xdg_session_type = vim.env.XDG_SESSION_TYPE
|
||||||
|
if not xdg_session_type then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
xdg_session_type = xdg_session_type:lower()
|
||||||
|
if xdg_session_type:find("x11") then
|
||||||
|
return "x11"
|
||||||
|
elseif xdg_session_type:find("wayland") then
|
||||||
|
return "wayland"
|
||||||
|
else
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return boolean
|
||||||
|
local function is_linux_desktop_gnome()
|
||||||
|
local cur_desktop = vim.env.XDG_CURRENT_DESKTOP
|
||||||
|
local session_desktop = vim.env.XDG_SESSION_DESKTOP
|
||||||
|
local idx = session_desktop and session_desktop:lower():find("gnome")
|
||||||
|
or cur_desktop and cur_desktop:lower():find("gnome")
|
||||||
|
return idx ~= nil or cur_desktop == "X-Cinnamon" or cur_desktop == "XFCE"
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param winid integer
|
||||||
|
---@param entry oil.InternalEntry
|
||||||
|
---@param column_defs oil.ColumnSpec[]
|
||||||
|
---@param adapter oil.Adapter
|
||||||
|
---@param bufnr integer
|
||||||
|
local function write_pasted(winid, entry, column_defs, adapter, bufnr)
|
||||||
|
local col_width = {}
|
||||||
|
for i in ipairs(column_defs) do
|
||||||
|
col_width[i + 1] = 1
|
||||||
|
end
|
||||||
|
local line_table =
|
||||||
|
{ view.format_entry_cols(entry, column_defs, col_width, adapter, false, bufnr) }
|
||||||
|
local lines, _ = util.render_table(line_table, col_width)
|
||||||
|
local pos = vim.api.nvim_win_get_cursor(winid)
|
||||||
|
vim.api.nvim_buf_set_lines(bufnr, pos[1], pos[1], true, lines)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param parent_url string
|
||||||
|
---@param entry oil.InternalEntry
|
||||||
|
local function remove_entry_from_parent_buffer(parent_url, entry)
|
||||||
|
local bufnr = vim.fn.bufadd(parent_url)
|
||||||
|
assert(vim.api.nvim_buf_is_loaded(bufnr), "Expected parent buffer to be loaded during paste")
|
||||||
|
local adapter = assert(util.get_adapter(bufnr))
|
||||||
|
local column_defs = columns.get_supported_columns(adapter)
|
||||||
|
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
||||||
|
for i, line in ipairs(lines) do
|
||||||
|
local result = parser.parse_line(adapter, line, column_defs)
|
||||||
|
if result and result.entry == entry then
|
||||||
|
vim.api.nvim_buf_set_lines(bufnr, i - 1, i, false, {})
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local exported = util.export_entry(entry)
|
||||||
|
vim.notify(
|
||||||
|
string.format("Error: could not delete original file '%s'", exported.name),
|
||||||
|
vim.log.levels.ERROR
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param paths string[]
|
||||||
|
---@param delete_original? boolean
|
||||||
|
local function paste_paths(paths, delete_original)
|
||||||
|
local bufnr = vim.api.nvim_get_current_buf()
|
||||||
|
local scheme = "oil://"
|
||||||
|
local adapter = assert(config.get_adapter_by_scheme(scheme))
|
||||||
|
local column_defs = columns.get_supported_columns(scheme)
|
||||||
|
local winid = vim.api.nvim_get_current_win()
|
||||||
|
|
||||||
|
local parent_urls = {}
|
||||||
|
local pending_paths = {}
|
||||||
|
|
||||||
|
-- Handle as many paths synchronously as possible
|
||||||
|
for _, path in ipairs(paths) do
|
||||||
|
-- Trim the trailing slash off directories
|
||||||
|
if vim.endswith(path, "/") then
|
||||||
|
path = path:sub(1, -2)
|
||||||
|
end
|
||||||
|
|
||||||
|
local ori_entry = cache.get_entry_by_url(scheme .. path)
|
||||||
|
local parent_url = util.addslash(scheme .. vim.fs.dirname(path))
|
||||||
|
if ori_entry then
|
||||||
|
write_pasted(winid, ori_entry, column_defs, adapter, bufnr)
|
||||||
|
if delete_original then
|
||||||
|
remove_entry_from_parent_buffer(parent_url, ori_entry)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
parent_urls[parent_url] = true
|
||||||
|
table.insert(pending_paths, path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- If all paths could be handled synchronously, we're done
|
||||||
|
if #pending_paths == 0 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Process the remaining paths by asynchronously loading them
|
||||||
|
local cursor = vim.api.nvim_win_get_cursor(winid)
|
||||||
|
local complete_loading = util.cb_collect(#vim.tbl_keys(parent_urls), function(err)
|
||||||
|
if err then
|
||||||
|
vim.notify(string.format("Error loading parent directory: %s", err), vim.log.levels.ERROR)
|
||||||
|
else
|
||||||
|
-- Something in this process moves the cursor to the top of the window, so have to restore it
|
||||||
|
vim.api.nvim_win_set_cursor(winid, cursor)
|
||||||
|
|
||||||
|
for _, path in ipairs(pending_paths) do
|
||||||
|
local ori_entry = cache.get_entry_by_url(scheme .. path)
|
||||||
|
if ori_entry then
|
||||||
|
write_pasted(winid, ori_entry, column_defs, adapter, bufnr)
|
||||||
|
if delete_original then
|
||||||
|
local parent_url = util.addslash(scheme .. vim.fs.dirname(path))
|
||||||
|
remove_entry_from_parent_buffer(parent_url, ori_entry)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
vim.notify(
|
||||||
|
string.format("The pasted file '%s' could not be found", path),
|
||||||
|
vim.log.levels.ERROR
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
for parent_url, _ in pairs(parent_urls) do
|
||||||
|
local new_bufnr = vim.api.nvim_create_buf(false, false)
|
||||||
|
vim.api.nvim_buf_set_name(new_bufnr, parent_url)
|
||||||
|
oil.load_oil_buffer(new_bufnr)
|
||||||
|
util.run_after_load(new_bufnr, complete_loading)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return integer start
|
||||||
|
---@return integer end
|
||||||
|
local function range_from_selection()
|
||||||
|
-- [bufnum, lnum, col, off]; both row and column 1-indexed
|
||||||
|
local start = vim.fn.getpos("v")
|
||||||
|
local end_ = vim.fn.getpos(".")
|
||||||
|
local start_row = start[2]
|
||||||
|
local end_row = end_[2]
|
||||||
|
|
||||||
|
if start_row > end_row then
|
||||||
|
start_row, end_row = end_row, start_row
|
||||||
|
end
|
||||||
|
|
||||||
|
return start_row, end_row
|
||||||
|
end
|
||||||
|
|
||||||
|
M.copy_to_system_clipboard = function()
|
||||||
|
local dir = oil.get_current_dir()
|
||||||
|
if not dir then
|
||||||
|
vim.notify("System clipboard only works for local files", vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local entries = {}
|
||||||
|
local mode = vim.api.nvim_get_mode().mode
|
||||||
|
if mode == "v" or mode == "V" then
|
||||||
|
if fs.is_mac then
|
||||||
|
vim.notify(
|
||||||
|
"Copying multiple paths to clipboard is not supported on mac",
|
||||||
|
vim.log.levels.ERROR
|
||||||
|
)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local start_row, end_row = range_from_selection()
|
||||||
|
for i = start_row, end_row do
|
||||||
|
table.insert(entries, oil.get_entry_on_line(0, i))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- leave visual mode
|
||||||
|
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("<Esc>", true, false, true), "n", true)
|
||||||
|
else
|
||||||
|
table.insert(entries, oil.get_cursor_entry())
|
||||||
|
end
|
||||||
|
|
||||||
|
-- This removes holes in the list-like table
|
||||||
|
entries = vim.tbl_values(entries)
|
||||||
|
|
||||||
|
if #entries == 0 then
|
||||||
|
vim.notify("Could not find local file under cursor", vim.log.levels.WARN)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local paths = {}
|
||||||
|
for _, entry in ipairs(entries) do
|
||||||
|
table.insert(paths, dir .. entry.name)
|
||||||
|
end
|
||||||
|
local cmd = {}
|
||||||
|
local stdin
|
||||||
|
if fs.is_mac then
|
||||||
|
cmd = {
|
||||||
|
"osascript",
|
||||||
|
"-e",
|
||||||
|
"on run args",
|
||||||
|
"-e",
|
||||||
|
"set the clipboard to POSIX file (first item of args)",
|
||||||
|
"-e",
|
||||||
|
"end run",
|
||||||
|
paths[1],
|
||||||
|
}
|
||||||
|
elseif fs.is_linux then
|
||||||
|
local xdg_session_type = get_linux_session_type()
|
||||||
|
if xdg_session_type == "x11" then
|
||||||
|
vim.list_extend(cmd, { "xclip", "-i", "-selection", "clipboard" })
|
||||||
|
elseif xdg_session_type == "wayland" then
|
||||||
|
table.insert(cmd, "wl-copy")
|
||||||
|
else
|
||||||
|
vim.notify("System clipboard not supported, check $XDG_SESSION_TYPE", vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local urls = {}
|
||||||
|
for _, path in ipairs(paths) do
|
||||||
|
table.insert(urls, "file://" .. path)
|
||||||
|
end
|
||||||
|
if is_linux_desktop_gnome() then
|
||||||
|
stdin = string.format("copy\n%s\0", table.concat(urls, "\n"))
|
||||||
|
vim.list_extend(cmd, { "-t", "x-special/gnome-copied-files" })
|
||||||
|
else
|
||||||
|
stdin = table.concat(urls, "\n") .. "\n"
|
||||||
|
vim.list_extend(cmd, { "-t", "text/uri-list" })
|
||||||
|
end
|
||||||
|
else
|
||||||
|
vim.notify("System clipboard not supported on Windows", vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if vim.fn.executable(cmd[1]) == 0 then
|
||||||
|
vim.notify(string.format("Could not find executable '%s'", cmd[1]), vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local stderr = ""
|
||||||
|
local jid = vim.fn.jobstart(cmd, {
|
||||||
|
stderr_buffered = true,
|
||||||
|
on_stderr = function(_, data)
|
||||||
|
stderr = table.concat(data, "\n")
|
||||||
|
end,
|
||||||
|
on_exit = function(j, exit_code)
|
||||||
|
if exit_code ~= 0 then
|
||||||
|
vim.notify(
|
||||||
|
string.format("Error copying '%s' to system clipboard\n%s", vim.inspect(paths), stderr),
|
||||||
|
vim.log.levels.ERROR
|
||||||
|
)
|
||||||
|
else
|
||||||
|
if #paths == 1 then
|
||||||
|
vim.notify(string.format("Copied '%s' to system clipboard", paths[1]))
|
||||||
|
else
|
||||||
|
vim.notify(string.format("Copied %d files to system clipboard", #paths))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
assert(jid > 0, "Failed to start job")
|
||||||
|
if stdin then
|
||||||
|
vim.api.nvim_chan_send(jid, stdin)
|
||||||
|
vim.fn.chanclose(jid, "stdin")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param lines string[]
|
||||||
|
---@return string[]
|
||||||
|
local function handle_paste_output_mac(lines)
|
||||||
|
local ret = {}
|
||||||
|
for _, line in ipairs(lines) do
|
||||||
|
if not line:match("^%s*$") then
|
||||||
|
table.insert(ret, line)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param lines string[]
|
||||||
|
---@return string[]
|
||||||
|
local function handle_paste_output_linux(lines)
|
||||||
|
local ret = {}
|
||||||
|
for _, line in ipairs(lines) do
|
||||||
|
local path = line:match("^file://(.+)$")
|
||||||
|
if path then
|
||||||
|
table.insert(ret, util.url_unescape(path))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param delete_original? boolean Delete the source file after pasting
|
||||||
|
M.paste_from_system_clipboard = function(delete_original)
|
||||||
|
local dir = oil.get_current_dir()
|
||||||
|
if not dir then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local cmd = {}
|
||||||
|
local handle_paste_output
|
||||||
|
if fs.is_mac then
|
||||||
|
cmd = {
|
||||||
|
"osascript",
|
||||||
|
"-e",
|
||||||
|
"on run",
|
||||||
|
"-e",
|
||||||
|
"POSIX path of (the clipboard as «class furl»)",
|
||||||
|
"-e",
|
||||||
|
"end run",
|
||||||
|
}
|
||||||
|
handle_paste_output = handle_paste_output_mac
|
||||||
|
elseif fs.is_linux then
|
||||||
|
local xdg_session_type = get_linux_session_type()
|
||||||
|
if xdg_session_type == "x11" then
|
||||||
|
vim.list_extend(cmd, { "xclip", "-o", "-selection", "clipboard" })
|
||||||
|
elseif xdg_session_type == "wayland" then
|
||||||
|
table.insert(cmd, "wl-paste")
|
||||||
|
else
|
||||||
|
vim.notify("System clipboard not supported, check $XDG_SESSION_TYPE", vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if is_linux_desktop_gnome() then
|
||||||
|
vim.list_extend(cmd, { "-t", "x-special/gnome-copied-files" })
|
||||||
|
else
|
||||||
|
vim.list_extend(cmd, { "-t", "text/uri-list" })
|
||||||
|
end
|
||||||
|
handle_paste_output = handle_paste_output_linux
|
||||||
|
else
|
||||||
|
vim.notify("System clipboard not supported on Windows", vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local paths
|
||||||
|
local stderr = ""
|
||||||
|
if vim.fn.executable(cmd[1]) == 0 then
|
||||||
|
vim.notify(string.format("Could not find executable '%s'", cmd[1]), vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local jid = vim.fn.jobstart(cmd, {
|
||||||
|
stdout_buffered = true,
|
||||||
|
stderr_buffered = true,
|
||||||
|
on_stdout = function(j, data)
|
||||||
|
local lines = vim.split(table.concat(data, "\n"), "\r?\n")
|
||||||
|
paths = handle_paste_output(lines)
|
||||||
|
end,
|
||||||
|
on_stderr = function(_, data)
|
||||||
|
stderr = table.concat(data, "\n")
|
||||||
|
end,
|
||||||
|
on_exit = function(j, exit_code)
|
||||||
|
if exit_code ~= 0 or not paths then
|
||||||
|
vim.notify(
|
||||||
|
string.format("Error pasting from system clipboard: %s", stderr),
|
||||||
|
vim.log.levels.ERROR
|
||||||
|
)
|
||||||
|
elseif #paths == 0 then
|
||||||
|
vim.notify("No valid files found in system clipboard", vim.log.levels.WARN)
|
||||||
|
else
|
||||||
|
paste_paths(paths, delete_original)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
assert(jid > 0, "Failed to start job")
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
|
|
@ -53,7 +53,7 @@ M.get_supported_columns = function(adapter_or_scheme)
|
||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
local EMPTY = { "-", "Comment" }
|
local EMPTY = { "-", "OilEmpty" }
|
||||||
|
|
||||||
M.EMPTY = EMPTY
|
M.EMPTY = EMPTY
|
||||||
|
|
||||||
|
|
@ -98,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
|
||||||
|
|
||||||
|
|
@ -200,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
|
||||||
|
|
@ -228,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", {
|
||||||
|
|
@ -256,14 +256,16 @@ M.register("name", {
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
if config.view_options.case_insensitive then
|
local memo = {}
|
||||||
return function(entry)
|
return function(entry)
|
||||||
return entry[FIELD_NAME]:gsub("%d+", pad_number):lower()
|
if memo[entry] == nil then
|
||||||
end
|
local name = entry[FIELD_NAME]:gsub("0*(%d+)", adjust_number)
|
||||||
else
|
if config.view_options.case_insensitive then
|
||||||
return function(entry)
|
name = name:lower()
|
||||||
return entry[FIELD_NAME]:gsub("%d+", pad_number)
|
end
|
||||||
|
memo[entry] = name
|
||||||
end
|
end
|
||||||
|
return memo[entry]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ local default_config = {
|
||||||
["-"] = { "actions.parent", mode = "n" },
|
["-"] = { "actions.parent", mode = "n" },
|
||||||
["_"] = { "actions.open_cwd", mode = "n" },
|
["_"] = { "actions.open_cwd", mode = "n" },
|
||||||
["`"] = { "actions.cd", mode = "n" },
|
["`"] = { "actions.cd", mode = "n" },
|
||||||
["~"] = { "actions.cd", opts = { scope = "tab" }, mode = "n" },
|
["g~"] = { "actions.cd", opts = { scope = "tab" }, mode = "n" },
|
||||||
["gs"] = { "actions.change_sort", mode = "n" },
|
["gs"] = { "actions.change_sort", mode = "n" },
|
||||||
["gx"] = "actions.open_external",
|
["gx"] = "actions.open_external",
|
||||||
["g."] = { "actions.toggle_hidden", mode = "n" },
|
["g."] = { "actions.toggle_hidden", mode = "n" },
|
||||||
|
|
@ -107,6 +107,8 @@ local default_config = {
|
||||||
},
|
},
|
||||||
-- Extra arguments to pass to SCP when moving/copying files over SSH
|
-- Extra arguments to pass to SCP when moving/copying files over SSH
|
||||||
extra_scp_args = {},
|
extra_scp_args = {},
|
||||||
|
-- Extra arguments to pass to aws s3 when creating/deleting/moving/copying files using aws s3
|
||||||
|
extra_s3_args = {},
|
||||||
-- EXPERIMENTAL support for performing file operations with git
|
-- EXPERIMENTAL support for performing file operations with git
|
||||||
git = {
|
git = {
|
||||||
-- Return true to automatically git add/mv/rm files
|
-- Return true to automatically git add/mv/rm files
|
||||||
|
|
@ -127,7 +129,7 @@ local default_config = {
|
||||||
-- max_width and max_height can be integers or a float between 0 and 1 (e.g. 0.4 for 40%)
|
-- 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,
|
||||||
},
|
},
|
||||||
|
|
@ -172,7 +174,7 @@ local default_config = {
|
||||||
min_height = { 5, 0.1 },
|
min_height = { 5, 0.1 },
|
||||||
-- optionally define an integer/float for the exact height of the preview window
|
-- optionally define an integer/float for the exact height of the preview window
|
||||||
height = nil,
|
height = nil,
|
||||||
border = "rounded",
|
border = nil,
|
||||||
win_options = {
|
win_options = {
|
||||||
winblend = 0,
|
winblend = 0,
|
||||||
},
|
},
|
||||||
|
|
@ -185,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,
|
||||||
|
|
@ -193,20 +195,25 @@ local default_config = {
|
||||||
},
|
},
|
||||||
-- Configuration for the floating SSH window
|
-- Configuration for the floating SSH window
|
||||||
ssh = {
|
ssh = {
|
||||||
border = "rounded",
|
border = nil,
|
||||||
},
|
},
|
||||||
-- Configuration for the floating keymaps help window
|
-- Configuration for the floating keymaps help window
|
||||||
keymaps_help = {
|
keymaps_help = {
|
||||||
border = "rounded",
|
border = nil,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
-- The adapter API hasn't really stabilized yet. We're not ready to advertise or encourage people to
|
-- The adapter API hasn't really stabilized yet. We're not ready to advertise or encourage people to
|
||||||
-- write their own adapters, and so there's no real reason to edit these config options. For that
|
-- write their own adapters, and so there's no real reason to edit these config options. For that
|
||||||
-- reason, I'm taking them out of the section above so they won't show up in the autogen docs.
|
-- reason, I'm taking them out of the section above so they won't show up in the autogen docs.
|
||||||
|
|
||||||
|
-- not "oil-s3://" on older neovim versions, since it doesn't open buffers correctly with a number
|
||||||
|
-- in the name
|
||||||
|
local oil_s3_string = vim.fn.has("nvim-0.12") == 1 and "oil-s3://" or "oil-sss://"
|
||||||
default_config.adapters = {
|
default_config.adapters = {
|
||||||
["oil://"] = "files",
|
["oil://"] = "files",
|
||||||
["oil-ssh://"] = "ssh",
|
["oil-ssh://"] = "ssh",
|
||||||
|
[oil_s3_string] = "s3",
|
||||||
["oil-trash://"] = "trash",
|
["oil-trash://"] = "trash",
|
||||||
}
|
}
|
||||||
default_config.adapter_aliases = {}
|
default_config.adapter_aliases = {}
|
||||||
|
|
@ -217,7 +224,6 @@ default_config.view_options.highlight_filename = nil
|
||||||
---@class oil.Config
|
---@class oil.Config
|
||||||
---@field adapters table<string, string> Hidden from SetupOpts
|
---@field adapters table<string, string> Hidden from SetupOpts
|
||||||
---@field adapter_aliases table<string, string> Hidden from SetupOpts
|
---@field adapter_aliases table<string, string> Hidden from SetupOpts
|
||||||
---@field trash_command? string Deprecated option that we should clean up soon
|
|
||||||
---@field silence_scp_warning? boolean Undocumented option
|
---@field silence_scp_warning? boolean Undocumented option
|
||||||
---@field default_file_explorer boolean
|
---@field default_file_explorer boolean
|
||||||
---@field columns oil.ColumnSpec[]
|
---@field columns oil.ColumnSpec[]
|
||||||
|
|
@ -234,6 +240,7 @@ default_config.view_options.highlight_filename = nil
|
||||||
---@field use_default_keymaps boolean
|
---@field use_default_keymaps boolean
|
||||||
---@field view_options oil.ViewOptions
|
---@field view_options oil.ViewOptions
|
||||||
---@field extra_scp_args string[]
|
---@field extra_scp_args string[]
|
||||||
|
---@field extra_s3_args string[]
|
||||||
---@field git oil.GitOptions
|
---@field git oil.GitOptions
|
||||||
---@field float oil.FloatWindowConfig
|
---@field float oil.FloatWindowConfig
|
||||||
---@field preview_win oil.PreviewWindowConfig
|
---@field preview_win oil.PreviewWindowConfig
|
||||||
|
|
@ -262,6 +269,7 @@ local M = {}
|
||||||
---@field use_default_keymaps? boolean Set to false to disable all of the above keymaps
|
---@field use_default_keymaps? boolean Set to false to disable all of the above keymaps
|
||||||
---@field view_options? oil.SetupViewOptions Configure which files are shown and how they are shown.
|
---@field view_options? oil.SetupViewOptions Configure which files are shown and how they are shown.
|
||||||
---@field extra_scp_args? string[] Extra arguments to pass to SCP when moving/copying files over SSH
|
---@field extra_scp_args? string[] Extra arguments to pass to SCP when moving/copying files over SSH
|
||||||
|
---@field extra_s3_args? string[] Extra arguments to pass to aws s3 when moving/copying files using aws s3
|
||||||
---@field git? oil.SetupGitOptions EXPERIMENTAL support for performing file operations with git
|
---@field git? oil.SetupGitOptions EXPERIMENTAL support for performing file operations with git
|
||||||
---@field float? oil.SetupFloatWindowConfig Configuration for the floating window in oil.open_float
|
---@field float? oil.SetupFloatWindowConfig Configuration for the floating window in oil.open_float
|
||||||
---@field preview_win? oil.SetupPreviewWindowConfig Configuration for the file preview window
|
---@field preview_win? oil.SetupPreviewWindowConfig Configuration for the file preview window
|
||||||
|
|
@ -394,13 +402,6 @@ local M = {}
|
||||||
M.setup = function(opts)
|
M.setup = function(opts)
|
||||||
opts = opts or {}
|
opts = opts or {}
|
||||||
|
|
||||||
if opts.trash_command then
|
|
||||||
vim.notify(
|
|
||||||
"[oil.nvim] trash_command is deprecated. Use built-in trash functionality instead (:help oil-trash).\nCompatibility will be removed on 2025-06-01.",
|
|
||||||
vim.log.levels.WARN
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
local new_conf = vim.tbl_deep_extend("keep", opts, default_config)
|
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 {}
|
||||||
|
|
@ -412,6 +413,17 @@ M.setup = function(opts)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Backwards compatibility for old versions that don't support winborder
|
||||||
|
if vim.fn.has("nvim-0.11") == 0 then
|
||||||
|
new_conf = vim.tbl_deep_extend("keep", new_conf, {
|
||||||
|
float = { border = "rounded" },
|
||||||
|
confirmation = { border = "rounded" },
|
||||||
|
progress = { border = "rounded" },
|
||||||
|
ssh = { border = "rounded" },
|
||||||
|
keymaps_help = { border = "rounded" },
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
-- Backwards compatibility. We renamed the 'preview' window config to be called 'confirmation'.
|
-- Backwards compatibility. We renamed the 'preview' window config to be called 'confirmation'.
|
||||||
if opts.preview and not opts.confirmation then
|
if opts.preview and not opts.confirmation then
|
||||||
new_conf.confirmation = vim.tbl_deep_extend("keep", opts.preview, default_config.confirmation)
|
new_conf.confirmation = vim.tbl_deep_extend("keep", opts.preview, default_config.confirmation)
|
||||||
|
|
@ -464,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
|
||||||
|
|
@ -478,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
|
||||||
|
|
|
||||||
|
|
@ -218,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
|
||||||
|
|
@ -320,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
|
||||||
|
|
|
||||||
|
|
@ -30,8 +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
|
||||||
|
|
||||||
local load_oil_buffer
|
|
||||||
|
|
||||||
---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
|
||||||
|
|
@ -224,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]
|
||||||
|
|
@ -344,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
|
||||||
|
|
||||||
|
|
@ -545,6 +548,8 @@ M.open_preview = function(opts, callback)
|
||||||
end
|
end
|
||||||
|
|
||||||
util.get_edit_path(bufnr, entry, function(normalized_url)
|
util.get_edit_path(bufnr, entry, function(normalized_url)
|
||||||
|
local mc = package.loaded["multicursor-nvim"]
|
||||||
|
local has_multicursors = mc and mc.hasCursors()
|
||||||
local is_visual_mode = util.is_visual_mode()
|
local is_visual_mode = util.is_visual_mode()
|
||||||
if preview_win then
|
if preview_win then
|
||||||
if is_visual_mode then
|
if is_visual_mode then
|
||||||
|
|
@ -593,7 +598,7 @@ M.open_preview = function(opts, callback)
|
||||||
-- If we called open_preview during an autocmd, then the edit command may not trigger the
|
-- 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.
|
-- BufReadCmd to load the buffer. So we need to do it manually.
|
||||||
if util.is_oil_bufnr(filebufnr) then
|
if util.is_oil_bufnr(filebufnr) then
|
||||||
load_oil_buffer(filebufnr)
|
M.load_oil_buffer(filebufnr)
|
||||||
end
|
end
|
||||||
|
|
||||||
vim.api.nvim_set_option_value("previewwindow", true, { scope = "local", win = 0 })
|
vim.api.nvim_set_option_value("previewwindow", true, { scope = "local", win = 0 })
|
||||||
|
|
@ -603,7 +608,10 @@ M.open_preview = function(opts, callback)
|
||||||
end
|
end
|
||||||
vim.w.oil_entry_id = entry.id
|
vim.w.oil_entry_id = entry.id
|
||||||
vim.w.oil_source_win = prev_win
|
vim.w.oil_source_win = prev_win
|
||||||
if is_visual_mode then
|
if has_multicursors then
|
||||||
|
hack_set_win(prev_win)
|
||||||
|
mc.restoreCursors()
|
||||||
|
elseif is_visual_mode then
|
||||||
hack_set_win(prev_win)
|
hack_set_win(prev_win)
|
||||||
-- Restore the visual selection
|
-- Restore the visual selection
|
||||||
vim.cmd.normal({ args = { "gv" }, bang = true })
|
vim.cmd.normal({ args = { "gv" }, bang = true })
|
||||||
|
|
@ -620,6 +628,7 @@ end
|
||||||
---@field split? "aboveleft"|"belowright"|"topleft"|"botright" Split modifier
|
---@field split? "aboveleft"|"belowright"|"topleft"|"botright" Split modifier
|
||||||
---@field tab? boolean Open the buffer in a new tab
|
---@field tab? boolean Open the buffer in a new tab
|
||||||
---@field close? boolean Close the original oil buffer once selection is made
|
---@field close? boolean Close the original oil buffer once selection is made
|
||||||
|
---@field handle_buffer_callback? fun(buf_id: integer) If defined, all other buffer related options here would be ignored. This callback allows you to take over the process of opening the buffer yourself.
|
||||||
|
|
||||||
---Select the entry under the cursor
|
---Select the entry under the cursor
|
||||||
---@param opts nil|oil.SelectOpts
|
---@param opts nil|oil.SelectOpts
|
||||||
|
|
@ -754,18 +763,24 @@ M.select = function(opts, callback)
|
||||||
local cmd = "buffer"
|
local cmd = "buffer"
|
||||||
if opts.tab then
|
if opts.tab then
|
||||||
vim.cmd.tabnew({ mods = mods })
|
vim.cmd.tabnew({ mods = mods })
|
||||||
|
-- Make sure the new buffer from tabnew gets cleaned up
|
||||||
|
vim.bo.bufhidden = "wipe"
|
||||||
elseif opts.split then
|
elseif opts.split then
|
||||||
cmd = "sbuffer"
|
cmd = "sbuffer"
|
||||||
end
|
end
|
||||||
---@diagnostic disable-next-line: param-type-mismatch
|
if opts.handle_buffer_callback ~= nil then
|
||||||
local ok, err = pcall(vim.cmd, {
|
opts.handle_buffer_callback(filebufnr)
|
||||||
cmd = cmd,
|
else
|
||||||
args = { filebufnr },
|
---@diagnostic disable-next-line: param-type-mismatch
|
||||||
mods = mods,
|
local ok, err = pcall(vim.cmd, {
|
||||||
})
|
cmd = cmd,
|
||||||
-- Ignore swapfile errors
|
args = { filebufnr },
|
||||||
if not ok and err and not err:match("^Vim:E325:") then
|
mods = mods,
|
||||||
vim.api.nvim_echo({ { err, "Error" } }, true, {})
|
})
|
||||||
|
-- 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
|
||||||
|
|
||||||
open_next_entry(cb)
|
open_next_entry(cb)
|
||||||
|
|
@ -818,6 +833,11 @@ end
|
||||||
---@private
|
---@private
|
||||||
M._get_highlights = function()
|
M._get_highlights = function()
|
||||||
return {
|
return {
|
||||||
|
{
|
||||||
|
name = "OilEmpty",
|
||||||
|
link = "Comment",
|
||||||
|
desc = "Empty column values",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name = "OilHidden",
|
name = "OilHidden",
|
||||||
link = "Comment",
|
link = "Comment",
|
||||||
|
|
@ -1013,8 +1033,9 @@ local function restore_alt_buf()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
load_oil_buffer = function(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")
|
||||||
|
|
@ -1117,9 +1138,9 @@ M.setup = function(opts)
|
||||||
|
|
||||||
config.setup(opts)
|
config.setup(opts)
|
||||||
set_colors()
|
set_colors()
|
||||||
vim.api.nvim_create_user_command("Oil", function(args)
|
local callback = function(args)
|
||||||
local util = require("oil.util")
|
local util = require("oil.util")
|
||||||
if args.smods.tab == 1 then
|
if args.smods.tab > 0 then
|
||||||
vim.cmd.tabnew()
|
vim.cmd.tabnew()
|
||||||
end
|
end
|
||||||
local float = false
|
local float = false
|
||||||
|
|
@ -1152,11 +1173,13 @@ M.setup = function(opts)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if not float and (args.smods.vertical or args.smods.split ~= "") then
|
if not float and (args.smods.vertical or args.smods.horizontal or args.smods.split ~= "") then
|
||||||
|
local range = args.count > 0 and { args.count } or nil
|
||||||
|
local cmdargs = { mods = { split = args.smods.split }, range = range }
|
||||||
if args.smods.vertical then
|
if args.smods.vertical then
|
||||||
vim.cmd.vsplit({ mods = { split = args.smods.split } })
|
vim.cmd.vsplit(cmdargs)
|
||||||
else
|
else
|
||||||
vim.cmd.split({ mods = { split = args.smods.split } })
|
vim.cmd.split(cmdargs)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -1172,7 +1195,12 @@ M.setup = function(opts)
|
||||||
open_opts.preview = {}
|
open_opts.preview = {}
|
||||||
end
|
end
|
||||||
M[method](path, open_opts)
|
M[method](path, open_opts)
|
||||||
end, { desc = "Open oil file browser on a directory", nargs = "*", complete = "dir" })
|
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
|
||||||
|
|
@ -1218,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", {
|
||||||
|
|
@ -1253,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,
|
||||||
|
|
@ -1281,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
|
||||||
|
|
@ -1389,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,
|
||||||
})
|
})
|
||||||
|
|
@ -1398,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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ M.show_help = function(keymaps)
|
||||||
local highlights = {}
|
local highlights = {}
|
||||||
local max_line = 1
|
local max_line = 1
|
||||||
for _, entry in ipairs(keymap_entries) do
|
for _, entry in ipairs(keymap_entries) do
|
||||||
local line = string.format(" %s %s", util.rpad(entry.str, max_lhs), entry.desc)
|
local line = string.format(" %s %s", util.pad_align(entry.str, max_lhs, "left"), entry.desc)
|
||||||
max_line = math.max(max_line, vim.api.nvim_strwidth(line))
|
max_line = math.max(max_line, vim.api.nvim_strwidth(line))
|
||||||
table.insert(lines, line)
|
table.insert(lines, line)
|
||||||
local start = 1
|
local start = 1
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,8 @@ M.set_loading = function(bufnr, is_loading)
|
||||||
M.set_loading(bufnr, false)
|
M.set_loading(bufnr, false)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local lines = { util.lpad("Loading", math.floor(width / 2) - 3), bar_iter() }
|
local lines =
|
||||||
|
{ util.pad_align("Loading", math.floor(width / 2) - 3, "right"), bar_iter() }
|
||||||
util.render_text(bufnr, lines)
|
util.render_text(bufnr, lines)
|
||||||
end)
|
end)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -68,24 +68,34 @@ local function get_matching_paths(client, filters, paths)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Some language servers use forward slashes as path separators on Windows (LuaLS)
|
-- Some language servers use forward slashes as path separators on Windows (LuaLS)
|
||||||
if fs.is_windows then
|
-- We no longer need this after 0.12: https://github.com/neovim/neovim/commit/322a6d305d088420b23071c227af07b7c1beb41a
|
||||||
|
if vim.fn.has("nvim-0.12") == 0 and fs.is_windows then
|
||||||
glob = glob:gsub("/", "\\")
|
glob = glob:gsub("/", "\\")
|
||||||
end
|
end
|
||||||
|
|
||||||
---@type string|vim.lpeg.Pattern
|
---@type string|vim.lpeg.Pattern
|
||||||
local glob_to_match = glob
|
local glob_to_match = glob
|
||||||
if vim.glob and vim.glob.to_lpeg then
|
if vim.glob and vim.glob.to_lpeg then
|
||||||
-- HACK around https://github.com/neovim/neovim/issues/28931
|
glob = glob:gsub("{(.-)}", function(s)
|
||||||
-- find alternations and sort them by length to try to match the longest first
|
local patterns = vim.split(s, ",")
|
||||||
if vim.fn.has("nvim-0.11") == 0 then
|
local filtered = {}
|
||||||
glob = glob:gsub("{(.*)}", function(s)
|
for _, pat in ipairs(patterns) do
|
||||||
local pieces = vim.split(s, ",")
|
if pat ~= "" then
|
||||||
table.sort(pieces, function(a, b)
|
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()
|
return a:len() > b:len()
|
||||||
end)
|
end)
|
||||||
return "{" .. table.concat(pieces, ",") .. "}"
|
end
|
||||||
end)
|
return "{" .. table.concat(filtered, ",") .. "}"
|
||||||
end
|
end)
|
||||||
|
|
||||||
glob_to_match = vim.glob.to_lpeg(glob)
|
glob_to_match = vim.glob.to_lpeg(glob)
|
||||||
end
|
end
|
||||||
|
|
@ -167,8 +177,13 @@ local function will_file_operation(method, capability_name, files, options)
|
||||||
}
|
}
|
||||||
end, matching_files),
|
end, matching_files),
|
||||||
}
|
}
|
||||||
---@diagnostic disable-next-line: invisible
|
local result, err
|
||||||
local result, err = client.request_sync(method, params, options.timeout_ms or 1000, 0)
|
if vim.fn.has("nvim-0.11") == 1 then
|
||||||
|
result, err = client:request_sync(method, params, options.timeout_ms or 1000, 0)
|
||||||
|
else
|
||||||
|
---@diagnostic disable-next-line: param-type-mismatch
|
||||||
|
result, err = client.request_sync(method, params, options.timeout_ms or 1000, 0)
|
||||||
|
end
|
||||||
if result and result.result then
|
if result and result.result then
|
||||||
if options.apply_edits ~= false then
|
if options.apply_edits ~= false then
|
||||||
vim.lsp.util.apply_workspace_edit(result.result, client.offset_encoding)
|
vim.lsp.util.apply_workspace_edit(result.result, client.offset_encoding)
|
||||||
|
|
@ -204,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
|
||||||
|
|
@ -280,9 +299,15 @@ function M.will_rename_files(files, options)
|
||||||
}
|
}
|
||||||
end, matching_files),
|
end, matching_files),
|
||||||
}
|
}
|
||||||
local result, err =
|
local result, err
|
||||||
---@diagnostic disable-next-line: invisible
|
if vim.fn.has("nvim-0.11") == 1 then
|
||||||
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)
|
||||||
|
|
@ -313,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
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ M.create_actions_from_diffs = function(all_diffs)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
for bufnr, diffs in pairs(all_diffs) do
|
for bufnr, diffs in pairs(all_diffs) do
|
||||||
local adapter = util.get_adapter(bufnr)
|
local adapter = util.get_adapter(bufnr, true)
|
||||||
if not adapter then
|
if not adapter then
|
||||||
error("Missing adapter")
|
error("Missing adapter")
|
||||||
end
|
end
|
||||||
|
|
@ -519,7 +519,7 @@ M.try_write_changes = function(confirm, cb)
|
||||||
if vim.bo[bufnr].modified then
|
if vim.bo[bufnr].modified then
|
||||||
local diffs, errors = parser.parse(bufnr)
|
local diffs, errors = parser.parse(bufnr)
|
||||||
all_diffs[bufnr] = diffs
|
all_diffs[bufnr] = diffs
|
||||||
local adapter = assert(util.get_adapter(bufnr))
|
local adapter = assert(util.get_adapter(bufnr, true))
|
||||||
if adapter.filter_error then
|
if adapter.filter_error then
|
||||||
errors = vim.tbl_filter(adapter.filter_error, errors)
|
errors = vim.tbl_filter(adapter.filter_error, errors)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
183
lua/oil/util.lua
183
lua/oil/util.lua
|
|
@ -25,46 +25,63 @@ M.escape_filename = function(filename)
|
||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
local _url_escape_chars = {
|
local _url_escape_to_char = {
|
||||||
[" "] = "%20",
|
["20"] = " ",
|
||||||
["$"] = "%24",
|
["22"] = "“",
|
||||||
["&"] = "%26",
|
["23"] = "#",
|
||||||
["`"] = "%60",
|
["24"] = "$",
|
||||||
[":"] = "%3A",
|
["25"] = "%",
|
||||||
["<"] = "%3C",
|
["26"] = "&",
|
||||||
["="] = "%3D",
|
["27"] = "‘",
|
||||||
[">"] = "%3E",
|
["2B"] = "+",
|
||||||
["?"] = "%3F",
|
["2C"] = ",",
|
||||||
["["] = "%5B",
|
["2F"] = "/",
|
||||||
["\\"] = "%5C",
|
["3A"] = ":",
|
||||||
["]"] = "%5D",
|
["3B"] = ";",
|
||||||
["^"] = "%5E",
|
["3C"] = "<",
|
||||||
["{"] = "%7B",
|
["3D"] = "=",
|
||||||
["|"] = "%7C",
|
["3E"] = ">",
|
||||||
["}"] = "%7D",
|
["3F"] = "?",
|
||||||
["~"] = "%7E",
|
["40"] = "@",
|
||||||
["“"] = "%22",
|
["5B"] = "[",
|
||||||
["‘"] = "%27",
|
["5C"] = "\\",
|
||||||
["+"] = "%2B",
|
["5D"] = "]",
|
||||||
[","] = "%2C",
|
["5E"] = "^",
|
||||||
["#"] = "%23",
|
["60"] = "`",
|
||||||
["%"] = "%25",
|
["7B"] = "{",
|
||||||
["@"] = "%40",
|
["7C"] = "|",
|
||||||
["/"] = "%2F",
|
["7D"] = "}",
|
||||||
[";"] = "%3B",
|
["7E"] = "~",
|
||||||
}
|
}
|
||||||
|
local _char_to_url_escape = {}
|
||||||
|
for k, v in pairs(_url_escape_to_char) do
|
||||||
|
_char_to_url_escape[v] = "%" .. k
|
||||||
|
end
|
||||||
|
-- TODO this uri escape handling is very incomplete
|
||||||
|
|
||||||
---@param string string
|
---@param string string
|
||||||
---@return string
|
---@return string
|
||||||
M.url_escape = function(string)
|
M.url_escape = function(string)
|
||||||
return (string:gsub(".", _url_escape_chars))
|
return (string:gsub(".", _char_to_url_escape))
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param string string
|
||||||
|
---@return string
|
||||||
|
M.url_unescape = function(string)
|
||||||
|
return (
|
||||||
|
string:gsub("%%([0-9A-Fa-f][0-9A-Fa-f])", function(seq)
|
||||||
|
return _url_escape_to_char[seq:upper()] or ("%" .. seq)
|
||||||
|
end)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
|
---@param silent? boolean
|
||||||
---@return nil|oil.Adapter
|
---@return nil|oil.Adapter
|
||||||
M.get_adapter = function(bufnr)
|
M.get_adapter = function(bufnr, silent)
|
||||||
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
||||||
local adapter = config.get_adapter_by_scheme(bufname)
|
local adapter = config.get_adapter_by_scheme(bufname)
|
||||||
if not adapter then
|
if not adapter and not silent then
|
||||||
vim.notify_once(
|
vim.notify_once(
|
||||||
string.format("[oil] could not find adapter for buffer '%s://'", bufname),
|
string.format("[oil] could not find adapter for buffer '%s://'", bufname),
|
||||||
vim.log.levels.ERROR
|
vim.log.levels.ERROR
|
||||||
|
|
@ -74,34 +91,28 @@ M.get_adapter = function(bufnr)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param text string
|
---@param text string
|
||||||
---@param length nil|integer
|
---@param width integer|nil
|
||||||
---@return string
|
---@param align oil.ColumnAlign
|
||||||
M.rpad = function(text, length)
|
---@return string padded_text
|
||||||
if not length then
|
---@return integer left_padding
|
||||||
return text
|
M.pad_align = function(text, width, align)
|
||||||
|
if not width then
|
||||||
|
return text, 0
|
||||||
end
|
end
|
||||||
local textlen = vim.api.nvim_strwidth(text)
|
local text_width = vim.api.nvim_strwidth(text)
|
||||||
local delta = length - textlen
|
local total_pad = width - text_width
|
||||||
if delta > 0 then
|
if total_pad <= 0 then
|
||||||
return text .. string.rep(" ", delta)
|
return text, 0
|
||||||
else
|
|
||||||
return text
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
---@param text string
|
if align == "right" then
|
||||||
---@param length nil|integer
|
return string.rep(" ", total_pad) .. text, total_pad
|
||||||
---@return string
|
elseif align == "center" then
|
||||||
M.lpad = function(text, length)
|
local left_pad = math.floor(total_pad / 2)
|
||||||
if not length then
|
local right_pad = total_pad - left_pad
|
||||||
return text
|
return string.rep(" ", left_pad) .. text .. string.rep(" ", right_pad), left_pad
|
||||||
end
|
|
||||||
local textlen = vim.api.nvim_strwidth(text)
|
|
||||||
local delta = length - textlen
|
|
||||||
if delta > 0 then
|
|
||||||
return string.rep(" ", delta) .. text
|
|
||||||
else
|
else
|
||||||
return text
|
return text .. string.rep(" ", total_pad), 0
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -157,8 +168,10 @@ M.rename_buffer = function(src_bufnr, dest_buf_name)
|
||||||
-- This will fail if the dest buf name already exists
|
-- This will fail if the dest buf name already exists
|
||||||
local ok = pcall(vim.api.nvim_buf_set_name, src_bufnr, dest_buf_name)
|
local ok = pcall(vim.api.nvim_buf_set_name, src_bufnr, dest_buf_name)
|
||||||
if ok then
|
if ok then
|
||||||
-- Renaming the buffer creates a new buffer with the old name. Find it and delete it.
|
-- Renaming the buffer creates a new buffer with the old name.
|
||||||
vim.api.nvim_buf_delete(vim.fn.bufadd(bufname), {})
|
-- Find it and try to delete it, but don't if the buffer is in a context
|
||||||
|
-- where Neovim doesn't allow buffer modifications.
|
||||||
|
pcall(vim.api.nvim_buf_delete, vim.fn.bufadd(bufname), {})
|
||||||
if altbuf and vim.api.nvim_buf_is_valid(altbuf) then
|
if altbuf and vim.api.nvim_buf_is_valid(altbuf) then
|
||||||
vim.fn.setreg("#", altbuf)
|
vim.fn.setreg("#", altbuf)
|
||||||
end
|
end
|
||||||
|
|
@ -295,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
|
||||||
|
|
@ -313,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 }[]
|
||||||
|
|
@ -325,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
|
||||||
|
|
@ -346,7 +366,12 @@ M.set_highlights = function(bufnr, highlights)
|
||||||
local ns = vim.api.nvim_create_namespace("Oil")
|
local ns = vim.api.nvim_create_namespace("Oil")
|
||||||
vim.api.nvim_buf_clear_namespace(bufnr, ns, 0, -1)
|
vim.api.nvim_buf_clear_namespace(bufnr, ns, 0, -1)
|
||||||
for _, hl in ipairs(highlights) do
|
for _, hl in ipairs(highlights) do
|
||||||
vim.api.nvim_buf_add_highlight(bufnr, ns, unpack(hl))
|
local group, line, col_start, col_end = unpack(hl)
|
||||||
|
vim.api.nvim_buf_set_extmark(bufnr, ns, line, col_start, {
|
||||||
|
end_col = col_end,
|
||||||
|
hl_group = group,
|
||||||
|
strict = false,
|
||||||
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -500,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
|
||||||
|
|
@ -624,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
|
||||||
|
|
@ -656,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]
|
||||||
|
|
@ -802,11 +824,12 @@ M.send_to_quickfix = function(opts)
|
||||||
local action = opts.action == "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, {}, action, { 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({}, action, { 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", {})
|
||||||
vim.cmd.copen()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---@return boolean
|
---@return boolean
|
||||||
|
|
@ -887,7 +910,7 @@ M.get_edit_path = function(bufnr, entry, callback)
|
||||||
|
|
||||||
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
||||||
local scheme, dir = M.parse_url(bufname)
|
local scheme, dir = M.parse_url(bufname)
|
||||||
local adapter = M.get_adapter(bufnr)
|
local adapter = M.get_adapter(bufnr, true)
|
||||||
assert(scheme and dir and adapter)
|
assert(scheme and dir and adapter)
|
||||||
|
|
||||||
local url = scheme .. dir .. entry.name
|
local url = scheme .. dir .. entry.name
|
||||||
|
|
@ -944,8 +967,12 @@ M.read_file_to_scratch_buffer = function(path, preview_method)
|
||||||
vim.bo[bufnr].bufhidden = "wipe"
|
vim.bo[bufnr].bufhidden = "wipe"
|
||||||
vim.bo[bufnr].buftype = "nofile"
|
vim.bo[bufnr].buftype = "nofile"
|
||||||
|
|
||||||
local max_lines = preview_method == "fast_scratch" and vim.o.lines or nil
|
local has_lines, read_res
|
||||||
local has_lines, read_res = pcall(vim.fn.readfile, path, "", max_lines)
|
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 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)
|
local ok = pcall(vim.api.nvim_buf_set_lines, bufnr, 0, -1, false, lines)
|
||||||
|
|
|
||||||
|
|
@ -146,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
|
||||||
|
|
@ -257,21 +257,14 @@ 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
|
||||||
---@param mode false|"name"|"editable"
|
--- @param adapter oil.Adapter
|
||||||
local function constrain_cursor(mode)
|
--- @param mode false|"name"|"editable"
|
||||||
if not mode then
|
--- @param cur integer[]
|
||||||
return
|
--- @return integer[] | nil
|
||||||
end
|
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
|
||||||
|
|
@ -284,7 +277,45 @@ local function constrain_cursor(mode)
|
||||||
error(string.format('Unexpected value "%s" for option constrain_cursor', mode))
|
error(string.format('Unexpected value "%s" for option constrain_cursor', mode))
|
||||||
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
|
||||||
|
|
@ -296,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
|
||||||
|
|
@ -406,7 +437,7 @@ M.initialize = function(bufnr)
|
||||||
callback = function()
|
callback = function()
|
||||||
-- For some reason the cursor bounces back to its original position,
|
-- For some reason the cursor bounces back to its original position,
|
||||||
-- so we have to defer the call
|
-- so we have to defer the call
|
||||||
vim.schedule_wrap(constrain_cursor)(config.constrain_cursor)
|
vim.schedule_wrap(constrain_cursor)(bufnr, config.constrain_cursor)
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
vim.api.nvim_create_autocmd({ "CursorMoved", "ModeChanged" }, {
|
vim.api.nvim_create_autocmd({ "CursorMoved", "ModeChanged" }, {
|
||||||
|
|
@ -419,7 +450,7 @@ M.initialize = function(bufnr)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
constrain_cursor(config.constrain_cursor)
|
constrain_cursor(bufnr, config.constrain_cursor)
|
||||||
|
|
||||||
if config.preview_win.update_on_cursor_moved then
|
if config.preview_win.update_on_cursor_moved then
|
||||||
-- Debounce and update the preview window
|
-- Debounce and update the preview window
|
||||||
|
|
@ -456,7 +487,7 @@ M.initialize = function(bufnr)
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
local adapter = util.get_adapter(bufnr)
|
local adapter = util.get_adapter(bufnr, true)
|
||||||
|
|
||||||
-- Set up a watcher that will refresh the directory
|
-- Set up a watcher that will refresh the directory
|
||||||
if
|
if
|
||||||
|
|
@ -583,7 +614,7 @@ local function get_sort_function(adapter, num_entries)
|
||||||
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
|
||||||
|
|
@ -616,7 +647,7 @@ 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
|
||||||
|
|
@ -637,8 +668,11 @@ 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
|
||||||
|
|
@ -661,7 +695,7 @@ local function render_buffer(bufnr, opts)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local lines, highlights = util.render_table(line_table, col_width)
|
local lines, highlights = util.render_table(line_table, col_width, col_align)
|
||||||
|
|
||||||
vim.bo[bufnr].modifiable = true
|
vim.bo[bufnr].modifiable = true
|
||||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, lines)
|
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, lines)
|
||||||
|
|
@ -690,7 +724,7 @@ local function render_buffer(bufnr, opts)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
constrain_cursor("name")
|
constrain_cursor(bufnr, "name")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
@ -877,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
|
||||||
|
|
|
||||||
|
|
@ -110,11 +110,16 @@ class ColumnDef:
|
||||||
params: List["LuaParam"] = field(default_factory=list)
|
params: List["LuaParam"] = field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
HL = [
|
UNIVERSAL = [
|
||||||
LuaParam(
|
LuaParam(
|
||||||
"highlight",
|
"highlight",
|
||||||
"string|fun(value: string): string",
|
"string|fun(value: string): string",
|
||||||
"Highlight group, or function that returns a highlight group",
|
"Highlight group, or function that returns a highlight group",
|
||||||
|
),
|
||||||
|
LuaParam(
|
||||||
|
"align",
|
||||||
|
'"left"|"center"|"right"',
|
||||||
|
"Text alignment within the column",
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
TIME = [
|
TIME = [
|
||||||
|
|
@ -127,7 +132,7 @@ COL_DEFS = [
|
||||||
False,
|
False,
|
||||||
True,
|
True,
|
||||||
"The type of the entry (file, directory, link, etc)",
|
"The type of the entry (file, directory, link, etc)",
|
||||||
HL
|
UNIVERSAL
|
||||||
+ [LuaParam("icons", "table<string, string>", "Mapping of entry type to icon")],
|
+ [LuaParam("icons", "table<string, string>", "Mapping of entry type to icon")],
|
||||||
),
|
),
|
||||||
ColumnDef(
|
ColumnDef(
|
||||||
|
|
@ -136,7 +141,7 @@ COL_DEFS = [
|
||||||
False,
|
False,
|
||||||
False,
|
False,
|
||||||
"An icon for the entry's type (requires nvim-web-devicons)",
|
"An icon for the entry's type (requires nvim-web-devicons)",
|
||||||
HL
|
UNIVERSAL
|
||||||
+ [
|
+ [
|
||||||
LuaParam(
|
LuaParam(
|
||||||
"default_file",
|
"default_file",
|
||||||
|
|
@ -151,31 +156,31 @@ COL_DEFS = [
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
ColumnDef("size", "files, ssh", False, True, "The size of the file", HL + []),
|
ColumnDef("size", "files, ssh, s3", False, True, "The size of the file", UNIVERSAL + []),
|
||||||
ColumnDef(
|
ColumnDef(
|
||||||
"permissions",
|
"permissions",
|
||||||
"files, ssh",
|
"files, ssh",
|
||||||
True,
|
True,
|
||||||
False,
|
False,
|
||||||
"Access permissions of the file",
|
"Access permissions of the file",
|
||||||
HL + [],
|
UNIVERSAL + [],
|
||||||
),
|
),
|
||||||
ColumnDef(
|
ColumnDef(
|
||||||
"ctime", "files", False, True, "Change timestamp of the file", HL + TIME + []
|
"ctime", "files", False, True, "Change timestamp of the file", UNIVERSAL + TIME + []
|
||||||
),
|
),
|
||||||
ColumnDef(
|
ColumnDef(
|
||||||
"mtime", "files", False, True, "Last modified time of the file", HL + TIME + []
|
"mtime", "files", False, True, "Last modified time of the file", UNIVERSAL + TIME + []
|
||||||
),
|
),
|
||||||
ColumnDef(
|
ColumnDef(
|
||||||
"atime", "files", False, True, "Last access time of the file", HL + TIME + []
|
"atime", "files", False, True, "Last access time of the file", UNIVERSAL + TIME + []
|
||||||
),
|
),
|
||||||
ColumnDef(
|
ColumnDef(
|
||||||
"birthtime",
|
"birthtime",
|
||||||
"files",
|
"files, s3",
|
||||||
False,
|
False,
|
||||||
True,
|
True,
|
||||||
"The time the file was created",
|
"The time the file was created",
|
||||||
HL + TIME + [],
|
UNIVERSAL + TIME + [],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
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)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue