Compare commits
2 commits
docs/upstr
...
build/dev-
| Author | SHA1 | Date | |
|---|---|---|---|
| 50f1ade92c | |||
| 364b787578 |
116 changed files with 5080 additions and 5748 deletions
9
.busted
9
.busted
|
|
@ -1,9 +0,0 @@
|
||||||
return {
|
|
||||||
_all = {
|
|
||||||
lua = "nlua",
|
|
||||||
},
|
|
||||||
default = {
|
|
||||||
ROOT = { "./spec/" },
|
|
||||||
helper = "spec/minimal_init.lua",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
|
||||||
insert_final_newline = true
|
|
||||||
charset = utf-8
|
|
||||||
|
|
||||||
[*.lua]
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 2
|
|
||||||
1
.envrc
Normal file
1
.envrc
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
use flake
|
||||||
80
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
80
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
|
|
@ -1,80 +0,0 @@
|
||||||
name: Bug Report
|
|
||||||
description: Report a bug
|
|
||||||
title: 'bug: '
|
|
||||||
labels: [bug]
|
|
||||||
body:
|
|
||||||
- type: checkboxes
|
|
||||||
attributes:
|
|
||||||
label: Prerequisites
|
|
||||||
options:
|
|
||||||
- label:
|
|
||||||
I have searched [existing
|
|
||||||
issues](https://github.com/barrettruth/canola.nvim/issues)
|
|
||||||
required: true
|
|
||||||
- label: I have updated to the latest version
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: 'Neovim version'
|
|
||||||
description: 'Output of `nvim --version`'
|
|
||||||
render: text
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: input
|
|
||||||
attributes:
|
|
||||||
label: 'Operating system'
|
|
||||||
placeholder: 'e.g. Arch Linux, macOS 15, Ubuntu 24.04'
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Description
|
|
||||||
description: What happened? What did you expect?
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Steps to reproduce
|
|
||||||
description: Minimal steps to trigger the bug
|
|
||||||
value: |
|
|
||||||
1.
|
|
||||||
2.
|
|
||||||
3.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: 'Health check'
|
|
||||||
description: 'Output of `:checkhealth canola`'
|
|
||||||
render: text
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Minimal reproduction
|
|
||||||
description: |
|
|
||||||
Save the script below as `repro.lua`, edit if needed, and run:
|
|
||||||
```
|
|
||||||
nvim -u repro.lua
|
|
||||||
```
|
|
||||||
Confirm the bug reproduces with this config before submitting.
|
|
||||||
render: lua
|
|
||||||
value: |
|
|
||||||
vim.env.LAZY_STDPATH = '.repro'
|
|
||||||
load(vim.fn.system('curl -s https://raw.githubusercontent.com/folke/lazy.nvim/main/bootstrap.lua'))()
|
|
||||||
require('lazy.nvim').setup({
|
|
||||||
spec = {
|
|
||||||
{
|
|
||||||
'barrettruth/canola.nvim',
|
|
||||||
init = function()
|
|
||||||
vim.g.canola = {}
|
|
||||||
end,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
131
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
131
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
name: Bug Report
|
||||||
|
description: File a bug/issue
|
||||||
|
title: "bug: "
|
||||||
|
labels: [bug]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Before reporting a bug, make sure to search [existing issues](https://github.com/stevearc/oil.nvim/issues)
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Did you check the docs and existing issues?
|
||||||
|
options:
|
||||||
|
- label: I have read the docs
|
||||||
|
required: true
|
||||||
|
- label: I have searched the existing issues
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: "Neovim version (nvim -v)"
|
||||||
|
placeholder: "0.8.0 commit db1b0ee3b30f"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: "Operating system/version"
|
||||||
|
placeholder: "MacOS 11.5"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Describe the bug
|
||||||
|
description: A clear and concise description of what the bug is. Please include any related errors you see in Neovim.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: What is the severity of this bug?
|
||||||
|
options:
|
||||||
|
- minor (annoyance)
|
||||||
|
- tolerable (can work around it)
|
||||||
|
- breaking (some functionality is broken)
|
||||||
|
- blocking (cannot use plugin)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Steps To Reproduce
|
||||||
|
description: Steps to reproduce the behavior.
|
||||||
|
placeholder: |
|
||||||
|
1. nvim -u repro.lua
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Expected Behavior
|
||||||
|
description: A concise description of what you expected to happen.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Directory structure
|
||||||
|
description: The structure of the directory used to reproduce the bug
|
||||||
|
placeholder: |
|
||||||
|
a/b/foo.txt
|
||||||
|
a/bar.md
|
||||||
|
a/c/baz.txt
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Repro
|
||||||
|
description:
|
||||||
|
Minimal `init.lua` to reproduce this issue. Save as `repro.lua` and run with `nvim -u repro.lua`
|
||||||
|
This uses lazy.nvim (a plugin manager).
|
||||||
|
You can add your config with the `config` key the same way you can do with packer.nvim.
|
||||||
|
value: |
|
||||||
|
-- save as repro.lua
|
||||||
|
-- run with nvim -u repro.lua
|
||||||
|
-- DO NOT change the paths
|
||||||
|
local root = vim.fn.fnamemodify("./.repro", ":p")
|
||||||
|
|
||||||
|
-- set stdpaths to use .repro
|
||||||
|
for _, name in ipairs({ "config", "data", "state", "runtime", "cache" }) do
|
||||||
|
vim.env[("XDG_%s_HOME"):format(name:upper())] = root .. "/" .. name
|
||||||
|
end
|
||||||
|
|
||||||
|
-- bootstrap lazy
|
||||||
|
local lazypath = root .. "/plugins/lazy.nvim"
|
||||||
|
if not vim.loop.fs_stat(lazypath) then
|
||||||
|
vim.fn.system({
|
||||||
|
"git",
|
||||||
|
"clone",
|
||||||
|
"--filter=blob:none",
|
||||||
|
"--single-branch",
|
||||||
|
"https://github.com/folke/lazy.nvim.git",
|
||||||
|
lazypath,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
vim.opt.runtimepath:prepend(lazypath)
|
||||||
|
|
||||||
|
-- install plugins
|
||||||
|
local plugins = {
|
||||||
|
"folke/tokyonight.nvim",
|
||||||
|
{
|
||||||
|
"stevearc/oil.nvim",
|
||||||
|
config = function()
|
||||||
|
require("oil").setup({
|
||||||
|
-- add any needed settings here
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
-- add any other plugins here
|
||||||
|
}
|
||||||
|
require("lazy").setup(plugins, {
|
||||||
|
root = root .. "/plugins",
|
||||||
|
})
|
||||||
|
|
||||||
|
vim.cmd.colorscheme("tokyonight")
|
||||||
|
-- add anything else here
|
||||||
|
render: Lua
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Did you check the bug with a clean config?
|
||||||
|
options:
|
||||||
|
- label: I have confirmed that the bug reproduces with `nvim -u repro.lua` using the repro.lua file above.
|
||||||
|
required: true
|
||||||
5
.github/ISSUE_TEMPLATE/config.yaml
vendored
5
.github/ISSUE_TEMPLATE/config.yaml
vendored
|
|
@ -1,5 +0,0 @@
|
||||||
blank_issues_enabled: false
|
|
||||||
contact_links:
|
|
||||||
- name: Questions
|
|
||||||
url: https://github.com/barrettruth/canola.nvim/discussions
|
|
||||||
about: Ask questions and discuss ideas
|
|
||||||
30
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
30
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
|
|
@ -1,30 +0,0 @@
|
||||||
name: Feature Request
|
|
||||||
description: Suggest a feature
|
|
||||||
title: 'feat: '
|
|
||||||
labels: [enhancement]
|
|
||||||
body:
|
|
||||||
- type: checkboxes
|
|
||||||
attributes:
|
|
||||||
label: Prerequisites
|
|
||||||
options:
|
|
||||||
- label:
|
|
||||||
I have searched [existing
|
|
||||||
issues](https://github.com/barrettruth/canola.nvim/issues)
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Problem
|
|
||||||
description: What problem does this solve?
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Proposed solution
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Alternatives considered
|
|
||||||
43
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
43
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
name: Feature Request
|
||||||
|
description: Submit a feature request
|
||||||
|
title: "feature request: "
|
||||||
|
labels: [enhancement]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Before submitting a feature request, make sure to search for [existing requests](https://github.com/stevearc/oil.nvim/issues)
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Did you check existing requests?
|
||||||
|
options:
|
||||||
|
- label: I have searched the existing issues
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Describe the feature
|
||||||
|
description: A short summary of the feature you want
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Provide background
|
||||||
|
description: Describe the reasoning behind why you want the feature.
|
||||||
|
placeholder: I am trying to do X. My current workflow is Y.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: What is the significance of this feature?
|
||||||
|
options:
|
||||||
|
- nice to have
|
||||||
|
- strongly desired
|
||||||
|
- cannot use this plugin without it
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Additional details
|
||||||
|
description: Any additional information you would like to provide. Things you've tried, alternatives considered, examples from other plugins, etc.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
158
.github/scripts/upstream_digest.py
vendored
158
.github/scripts/upstream_digest.py
vendored
|
|
@ -1,158 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
from datetime import date, timedelta
|
|
||||||
|
|
||||||
|
|
||||||
UPSTREAM = "stevearc/oil.nvim"
|
|
||||||
UPSTREAM_MD = "doc/upstream.md"
|
|
||||||
|
|
||||||
PRS_HEADING = "## Open upstream PRs"
|
|
||||||
ISSUES_HEADING = "## Upstream issues"
|
|
||||||
|
|
||||||
|
|
||||||
def get_last_tracked_number():
|
|
||||||
try:
|
|
||||||
with open(UPSTREAM_MD) as f:
|
|
||||||
content = f.read()
|
|
||||||
numbers = re.findall(
|
|
||||||
r"\[#(\d+)\]\(https://github\.com/stevearc/oil\.nvim", content
|
|
||||||
)
|
|
||||||
if numbers:
|
|
||||||
return max(int(n) for n in numbers)
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def gh(*args):
|
|
||||||
result = subprocess.run(
|
|
||||||
["gh"] + list(args),
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
return result.stdout.strip()
|
|
||||||
|
|
||||||
|
|
||||||
def fetch_items(last_number, since_date):
|
|
||||||
merged_prs = json.loads(
|
|
||||||
gh(
|
|
||||||
"pr", "list",
|
|
||||||
"--repo", UPSTREAM,
|
|
||||||
"--state", "merged",
|
|
||||||
"--limit", "100",
|
|
||||||
"--json", "number,title,url",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
open_prs = json.loads(
|
|
||||||
gh(
|
|
||||||
"pr", "list",
|
|
||||||
"--repo", UPSTREAM,
|
|
||||||
"--state", "open",
|
|
||||||
"--limit", "100",
|
|
||||||
"--json", "number,title,createdAt,url",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
open_issues = json.loads(
|
|
||||||
gh(
|
|
||||||
"issue", "list",
|
|
||||||
"--repo", UPSTREAM,
|
|
||||||
"--state", "open",
|
|
||||||
"--limit", "100",
|
|
||||||
"--json", "number,title,createdAt,url",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if last_number is not None:
|
|
||||||
merged_prs = [x for x in merged_prs if x["number"] > last_number]
|
|
||||||
open_prs = [x for x in open_prs if x["number"] > last_number]
|
|
||||||
open_issues = [x for x in open_issues if x["number"] > last_number]
|
|
||||||
else:
|
|
||||||
cutoff = since_date.isoformat()
|
|
||||||
merged_prs = []
|
|
||||||
open_prs = [x for x in open_prs if x.get("createdAt", "") >= cutoff]
|
|
||||||
open_issues = [x for x in open_issues if x.get("createdAt", "") >= cutoff]
|
|
||||||
|
|
||||||
merged_prs.sort(key=lambda x: x["number"])
|
|
||||||
open_prs.sort(key=lambda x: x["number"])
|
|
||||||
open_issues.sort(key=lambda x: x["number"])
|
|
||||||
|
|
||||||
return merged_prs, open_prs, open_issues
|
|
||||||
|
|
||||||
|
|
||||||
def append_to_section(content, heading, new_rows):
|
|
||||||
lines = content.split("\n")
|
|
||||||
in_section = False
|
|
||||||
last_table_row = -1
|
|
||||||
|
|
||||||
for i, line in enumerate(lines):
|
|
||||||
if line.startswith("## "):
|
|
||||||
in_section = line.strip() == heading
|
|
||||||
if in_section and line.startswith("|"):
|
|
||||||
last_table_row = i
|
|
||||||
|
|
||||||
if last_table_row == -1:
|
|
||||||
return content
|
|
||||||
|
|
||||||
return "\n".join(
|
|
||||||
lines[: last_table_row + 1] + new_rows + lines[last_table_row + 1 :]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
last_number = get_last_tracked_number()
|
|
||||||
since_date = date.today() - timedelta(days=30)
|
|
||||||
|
|
||||||
merged_prs, open_prs, open_issues = fetch_items(last_number, since_date)
|
|
||||||
|
|
||||||
total = len(merged_prs) + len(open_prs) + len(open_issues)
|
|
||||||
if total == 0:
|
|
||||||
print("No new upstream activity.")
|
|
||||||
return
|
|
||||||
|
|
||||||
with open(UPSTREAM_MD) as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
pr_rows = []
|
|
||||||
for pr in open_prs:
|
|
||||||
pr_rows.append(f"| [#{pr['number']}]({pr['url']}) | {pr['title']} | open |")
|
|
||||||
for pr in merged_prs:
|
|
||||||
pr_rows.append(
|
|
||||||
f"| [#{pr['number']}]({pr['url']}) | {pr['title']} | merged — not cherry-picked |"
|
|
||||||
)
|
|
||||||
|
|
||||||
issue_rows = []
|
|
||||||
for issue in open_issues:
|
|
||||||
issue_rows.append(
|
|
||||||
f"| [#{issue['number']}]({issue['url']}) | open | {issue['title']} |"
|
|
||||||
)
|
|
||||||
|
|
||||||
if pr_rows:
|
|
||||||
content = append_to_section(content, PRS_HEADING, pr_rows)
|
|
||||||
if issue_rows:
|
|
||||||
content = append_to_section(content, ISSUES_HEADING, issue_rows)
|
|
||||||
|
|
||||||
with open(UPSTREAM_MD, "w") as f:
|
|
||||||
f.write(content)
|
|
||||||
|
|
||||||
github_output = os.environ.get("GITHUB_OUTPUT")
|
|
||||||
if github_output:
|
|
||||||
with open(github_output, "a") as f:
|
|
||||||
f.write("changed=true\n")
|
|
||||||
|
|
||||||
print(
|
|
||||||
f"Added {len(open_prs)} open PR(s), {len(merged_prs)} merged PR(s), "
|
|
||||||
f"{len(open_issues)} issue(s) to {UPSTREAM_MD}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
try:
|
|
||||||
main()
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
print(f"gh command failed: {e.stderr}", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
@ -8,7 +8,7 @@ jobs:
|
||||||
# issues in my "needs triage" filter.
|
# issues in my "needs triage" filter.
|
||||||
remove_question:
|
remove_question:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.event.sender.login != 'barrettruth'
|
if: github.event.sender.login != 'stevearc'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions-ecosystem/action-remove-labels@v1
|
- uses: actions-ecosystem/action-remove-labels@v1
|
||||||
|
|
|
||||||
16
.github/workflows/install_nvim.sh
vendored
Normal file
16
.github/workflows/install_nvim.sh
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
version="${NVIM_TAG-stable}"
|
||||||
|
dl_name="nvim-linux-x86_64.appimage"
|
||||||
|
# The appimage name changed in v0.10.4
|
||||||
|
if python -c 'from packaging.version import Version; import sys; sys.exit(not (Version(sys.argv[1]) < Version("v0.10.4")))' "$version" 2>/dev/null; then
|
||||||
|
dl_name="nvim.appimage"
|
||||||
|
fi
|
||||||
|
curl -sL "https://github.com/neovim/neovim/releases/download/${version}/${dl_name}" -o nvim.appimage
|
||||||
|
chmod +x nvim.appimage
|
||||||
|
./nvim.appimage --appimage-extract >/dev/null
|
||||||
|
rm -f nvim.appimage
|
||||||
|
mkdir -p ~/.local/share/nvim
|
||||||
|
mv squashfs-root ~/.local/share/nvim/appimage
|
||||||
|
sudo ln -s "$HOME/.local/share/nvim/appimage/AppRun" /usr/bin/nvim
|
||||||
|
/usr/bin/nvim --version
|
||||||
2
.github/workflows/luarocks.yaml
vendored
2
.github/workflows/luarocks.yaml
vendored
|
|
@ -7,7 +7,7 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
quality:
|
quality:
|
||||||
uses: ./.github/workflows/quality.yaml
|
uses: ./.github/workflows/tests.yml
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
needs: quality
|
needs: quality
|
||||||
|
|
|
||||||
85
.github/workflows/mirror_upstream_prs.yml
vendored
Normal file
85
.github/workflows/mirror_upstream_prs.yml
vendored
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
name: Mirror Upstream PRs
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 8 * * *"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
mirror:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Mirror new upstream PRs
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
script: |
|
||||||
|
const upstream = { owner: 'stevearc', repo: 'oil.nvim' };
|
||||||
|
const fork = { owner: 'barrettruth', repo: 'oil.nvim' };
|
||||||
|
|
||||||
|
const since = new Date();
|
||||||
|
since.setDate(since.getDate() - 1);
|
||||||
|
const sinceISO = since.toISOString();
|
||||||
|
|
||||||
|
const { data: prs } = await github.rest.pulls.list({
|
||||||
|
...upstream,
|
||||||
|
state: 'open',
|
||||||
|
sort: 'created',
|
||||||
|
direction: 'desc',
|
||||||
|
per_page: 30,
|
||||||
|
});
|
||||||
|
|
||||||
|
const recentPRs = prs.filter(pr => {
|
||||||
|
if (new Date(pr.created_at) < since) return false;
|
||||||
|
if (pr.user.login === 'dependabot[bot]') return false;
|
||||||
|
if (pr.user.login === 'dependabot-preview[bot]') return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (recentPRs.length === 0) {
|
||||||
|
console.log('No new upstream PRs in the last 24 hours.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: existingIssues } = await github.rest.issues.listForRepo({
|
||||||
|
...fork,
|
||||||
|
state: 'all',
|
||||||
|
labels: 'upstream/pr',
|
||||||
|
per_page: 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mirroredNumbers = new Set();
|
||||||
|
for (const issue of existingIssues) {
|
||||||
|
const match = issue.title.match(/^upstream#(\d+)/);
|
||||||
|
if (match) mirroredNumbers.add(parseInt(match[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const pr of recentPRs) {
|
||||||
|
if (mirroredNumbers.has(pr.number)) {
|
||||||
|
console.log(`Skipping PR #${pr.number} — already mirrored.`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const labels = pr.labels.map(l => l.name).join(', ') || 'none';
|
||||||
|
|
||||||
|
await github.rest.issues.create({
|
||||||
|
...fork,
|
||||||
|
title: `upstream#${pr.number}: ${pr.title}`,
|
||||||
|
body: [
|
||||||
|
`Mirrored from [stevearc/oil.nvim#${pr.number}](${pr.html_url}).`,
|
||||||
|
'',
|
||||||
|
`**Author:** @${pr.user.login}`,
|
||||||
|
`**Labels:** ${labels}`,
|
||||||
|
`**Created:** ${pr.created_at}`,
|
||||||
|
'',
|
||||||
|
'---',
|
||||||
|
'',
|
||||||
|
pr.body || '*No description provided.*',
|
||||||
|
].join('\n'),
|
||||||
|
labels: ['upstream/pr'],
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Created issue for upstream PR #${pr.number}: ${pr.title}`);
|
||||||
|
}
|
||||||
73
.github/workflows/quality.yaml
vendored
73
.github/workflows/quality.yaml
vendored
|
|
@ -1,73 +0,0 @@
|
||||||
name: quality
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_call:
|
|
||||||
pull_request:
|
|
||||||
branches: [main]
|
|
||||||
push:
|
|
||||||
branches: [main, ci/upstream-digest]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
changes:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
outputs:
|
|
||||||
lua: ${{ steps.changes.outputs.lua }}
|
|
||||||
markdown: ${{ steps.changes.outputs.markdown }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: dorny/paths-filter@v3
|
|
||||||
id: changes
|
|
||||||
with:
|
|
||||||
filters: |
|
|
||||||
lua:
|
|
||||||
- 'lua/**'
|
|
||||||
- 'plugin/**'
|
|
||||||
- 'spec/**'
|
|
||||||
- '*.lua'
|
|
||||||
- '.luarc.json'
|
|
||||||
- '*.toml'
|
|
||||||
- 'vim.yaml'
|
|
||||||
markdown:
|
|
||||||
- '*.md'
|
|
||||||
|
|
||||||
lua-format:
|
|
||||||
name: Lua Format Check
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: changes
|
|
||||||
if: ${{ needs.changes.outputs.lua == 'true' }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: cachix/install-nix-action@v31
|
|
||||||
- run: nix develop --command stylua --check lua spec
|
|
||||||
|
|
||||||
lua-lint:
|
|
||||||
name: Lua Lint Check
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: changes
|
|
||||||
if: ${{ needs.changes.outputs.lua == 'true' }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: cachix/install-nix-action@v31
|
|
||||||
- run: nix develop --command selene --display-style quiet .
|
|
||||||
|
|
||||||
lua-typecheck:
|
|
||||||
name: Lua Type Check
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: changes
|
|
||||||
if: ${{ needs.changes.outputs.lua == 'true' }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: stevearc/nvim-typecheck-action@v2
|
|
||||||
with:
|
|
||||||
path: lua
|
|
||||||
luals-version: 3.16.4
|
|
||||||
|
|
||||||
markdown-format:
|
|
||||||
name: Markdown Format Check
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: changes
|
|
||||||
if: ${{ needs.changes.outputs.markdown == 'true' }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: cachix/install-nix-action@v31
|
|
||||||
- run: nix develop --command prettier --check .
|
|
||||||
22
.github/workflows/test.yaml
vendored
22
.github/workflows/test.yaml
vendored
|
|
@ -1,22 +0,0 @@
|
||||||
name: test
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches: [main]
|
|
||||||
push:
|
|
||||||
branches: [main, ci/upstream-digest]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
nvim: [stable, nightly]
|
|
||||||
name: Test (Neovim ${{ matrix.nvim }})
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- uses: nvim-neorocks/nvim-busted-action@v1
|
|
||||||
with:
|
|
||||||
nvim_version: ${{ matrix.nvim }}
|
|
||||||
65
.github/workflows/tests.yml
vendored
Normal file
65
.github/workflows/tests.yml
vendored
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
name: Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
selene:
|
||||||
|
name: Selene
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: NTBBloodbath/selene-action@v1.0.0
|
||||||
|
with:
|
||||||
|
args: --display-style quiet .
|
||||||
|
|
||||||
|
stylua:
|
||||||
|
name: StyLua
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Stylua
|
||||||
|
uses: JohnnyMorganz/stylua-action@v4
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
version: v2.0.2
|
||||||
|
args: --check lua tests
|
||||||
|
|
||||||
|
typecheck:
|
||||||
|
name: typecheck
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: stevearc/nvim-typecheck-action@v2
|
||||||
|
with:
|
||||||
|
path: lua
|
||||||
|
|
||||||
|
run_tests:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- nvim_tag: v0.8.3
|
||||||
|
- nvim_tag: v0.9.4
|
||||||
|
- nvim_tag: v0.10.4
|
||||||
|
- nvim_tag: v0.11.0
|
||||||
|
|
||||||
|
name: Run tests
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
env:
|
||||||
|
NVIM_TAG: ${{ matrix.nvim_tag }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Neovim and dependencies
|
||||||
|
run: |
|
||||||
|
bash ./.github/workflows/install_nvim.sh
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: |
|
||||||
|
bash ./run_tests.sh
|
||||||
50
.github/workflows/upstream-digest.yaml
vendored
50
.github/workflows/upstream-digest.yaml
vendored
|
|
@ -1,50 +0,0 @@
|
||||||
name: upstream digest
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: '0 9 * * 1'
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
digest:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Update upstream tracker
|
|
||||||
id: digest
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: python3 .github/scripts/upstream_digest.py
|
|
||||||
|
|
||||||
- name: Format doc/upstream.md
|
|
||||||
if: steps.digest.outputs.changed == 'true'
|
|
||||||
run: npx --yes prettier --write doc/upstream.md
|
|
||||||
|
|
||||||
- name: Push and open PR if needed
|
|
||||||
if: steps.digest.outputs.changed == 'true'
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
|
||||||
BRANCH="ci/upstream-digest"
|
|
||||||
git config user.name "github-actions[bot]"
|
|
||||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
||||||
git checkout -b "${BRANCH}"
|
|
||||||
git add doc/upstream.md
|
|
||||||
git commit -m "docs(upstream): upstream digest $(date +%Y-%m-%d)"
|
|
||||||
git config --unset http.https://github.com/.extraheader
|
|
||||||
git remote set-url origin "https://x-access-token:${{ secrets.DIGEST_PAT }}@github.com/barrettruth/canola.nvim.git"
|
|
||||||
git push --force origin "${BRANCH}"
|
|
||||||
if ! GH_TOKEN="${{ secrets.GITHUB_TOKEN }}" gh pr list --head "${BRANCH}" --state open --json number --jq '.[0].number' | grep -q .; then
|
|
||||||
PR_URL=$(GH_TOKEN="${{ secrets.GITHUB_TOKEN }}" gh pr create \
|
|
||||||
--title "docs(upstream): upstream digest" \
|
|
||||||
--body "Automated weekly digest of new upstream activity. Triage by updating statuses and notes." \
|
|
||||||
--base main \
|
|
||||||
--head "${BRANCH}")
|
|
||||||
GH_TOKEN="${{ secrets.DIGEST_PAT }}" gh pr review "${PR_URL}" --approve
|
|
||||||
gh pr merge "${PR_URL}" --auto --squash
|
|
||||||
fi
|
|
||||||
56
.gitignore
vendored
56
.gitignore
vendored
|
|
@ -1,14 +1,48 @@
|
||||||
doc/tags
|
# Compiled Lua sources
|
||||||
doc/upstream.html
|
luac.out
|
||||||
*.log
|
|
||||||
.*cache*
|
# luarocks build files
|
||||||
CLAUDE.md
|
*.src.rock
|
||||||
.claude/
|
*.zip
|
||||||
node_modules/
|
*.tar.gz
|
||||||
|
|
||||||
|
# Object files
|
||||||
|
*.o
|
||||||
|
*.os
|
||||||
|
*.ko
|
||||||
|
*.obj
|
||||||
|
*.elf
|
||||||
|
|
||||||
|
# Precompiled Headers
|
||||||
|
*.gch
|
||||||
|
*.pch
|
||||||
|
|
||||||
|
# Libraries
|
||||||
|
*.lib
|
||||||
|
*.a
|
||||||
|
*.la
|
||||||
|
*.lo
|
||||||
|
*.def
|
||||||
|
*.exp
|
||||||
|
|
||||||
|
# Shared objects (inc. Windows DLLs)
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.so.*
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Executables
|
||||||
|
*.exe
|
||||||
|
*.out
|
||||||
|
*.app
|
||||||
|
*.i*86
|
||||||
|
*.x86_64
|
||||||
|
*.hex
|
||||||
|
|
||||||
.direnv/
|
.direnv/
|
||||||
.envrc
|
.testenv/
|
||||||
venv/
|
doc/tags
|
||||||
perf/tmp/
|
scripts/nvim-typecheck-action
|
||||||
scripts/benchmark.nvim
|
scripts/benchmark.nvim
|
||||||
|
perf/tmp/
|
||||||
profile.json
|
profile.json
|
||||||
.worktrees/
|
|
||||||
|
|
|
||||||
0
.gitmodules
vendored
Normal file
0
.gitmodules
vendored
Normal file
|
|
@ -3,14 +3,7 @@
|
||||||
"version": "LuaJIT",
|
"version": "LuaJIT",
|
||||||
"pathStrict": true
|
"pathStrict": true
|
||||||
},
|
},
|
||||||
"workspace": {
|
|
||||||
"checkThirdParty": false,
|
|
||||||
"ignoreDir": [".direnv"]
|
|
||||||
},
|
|
||||||
"type": {
|
"type": {
|
||||||
"checkTableShape": true
|
"checkTableShape": true
|
||||||
},
|
|
||||||
"completion": {
|
|
||||||
"callSnippet": "Replace"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
{
|
|
||||||
"proseWrap": "always",
|
|
||||||
"printWidth": 80,
|
|
||||||
"tabWidth": 2,
|
|
||||||
"useTabs": false,
|
|
||||||
"trailingComma": "none",
|
|
||||||
"semi": false,
|
|
||||||
"singleQuote": true
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
column_width = 100
|
column_width = 100
|
||||||
line_endings = "Unix"
|
|
||||||
indent_type = "Spaces"
|
indent_type = "Spaces"
|
||||||
indent_width = 2
|
indent_width = 2
|
||||||
quote_style = "AutoPreferSingle"
|
|
||||||
call_parentheses = "Always"
|
|
||||||
[sort_requires]
|
[sort_requires]
|
||||||
enabled = true
|
enabled = true
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
.direnv/
|
|
||||||
1
LICENSE
1
LICENSE
|
|
@ -1,7 +1,6 @@
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2022 Steven Arcangeli
|
Copyright (c) 2022 Steven Arcangeli
|
||||||
Copyright (c) 2025 Barrett Ruth
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
|
||||||
48
Makefile
Normal file
48
Makefile
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
## help: print this help message
|
||||||
|
.PHONY: help
|
||||||
|
help:
|
||||||
|
@echo 'Usage:'
|
||||||
|
@sed -n 's/^##//p' ${MAKEFILE_LIST} | column -t -s ':' | sed -e 's/^/ /'
|
||||||
|
|
||||||
|
## all: lint and run tests
|
||||||
|
.PHONY: all
|
||||||
|
all: lint test
|
||||||
|
|
||||||
|
## test: run tests
|
||||||
|
.PHONY: test
|
||||||
|
test:
|
||||||
|
./run_tests.sh
|
||||||
|
|
||||||
|
## lint: run linters and LuaLS typechecking
|
||||||
|
.PHONY: lint
|
||||||
|
lint: scripts/nvim-typecheck-action
|
||||||
|
./scripts/nvim-typecheck-action/typecheck.sh --workdir scripts/nvim-typecheck-action lua
|
||||||
|
selene --display-style quiet .
|
||||||
|
stylua --check lua tests
|
||||||
|
|
||||||
|
## profile: use LuaJIT profiler to profile the plugin
|
||||||
|
.PHONY: profile
|
||||||
|
profile: scripts/benchmark.nvim
|
||||||
|
nvim --clean -u perf/bootstrap.lua -c 'lua jit_profile()'
|
||||||
|
|
||||||
|
## flame_profile: create a trace in the chrome profiler format
|
||||||
|
.PHONY: flame_profile
|
||||||
|
flame_profile: scripts/benchmark.nvim
|
||||||
|
nvim --clean -u perf/bootstrap.lua -c 'lua flame_profile()'
|
||||||
|
|
||||||
|
## benchmark: benchmark performance opening directory with many files
|
||||||
|
.PHONY: benchmark
|
||||||
|
benchmark: scripts/benchmark.nvim
|
||||||
|
nvim --clean -u perf/bootstrap.lua -c 'lua benchmark()'
|
||||||
|
@cat perf/tmp/benchmark.txt
|
||||||
|
|
||||||
|
scripts/nvim-typecheck-action:
|
||||||
|
git clone https://github.com/stevearc/nvim-typecheck-action scripts/nvim-typecheck-action
|
||||||
|
|
||||||
|
scripts/benchmark.nvim:
|
||||||
|
git clone https://github.com/stevearc/benchmark.nvim scripts/benchmark.nvim
|
||||||
|
|
||||||
|
## clean: reset the repository to a clean state
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
rm -rf scripts/nvim-typecheck-action .testenv perf/tmp profile.json
|
||||||
166
README.md
166
README.md
|
|
@ -1,109 +1,145 @@
|
||||||
# canola.nvim
|
# oil.nvim
|
||||||
|
|
||||||
A refined [oil.nvim](https://github.com/stevearc/oil.nvim) — edit your
|
A [vim-vinegar](https://github.com/tpope/vim-vinegar) like file explorer that lets you edit your filesystem like a normal Neovim buffer.
|
||||||
filesystem like a buffer, with bug fixes and community PRs that haven't landed
|
|
||||||
upstream.
|
|
||||||
|
|
||||||
[Upstream tracker](doc/upstream.md) — full PR and issue triage against
|
|
||||||
[oil.nvim](https://github.com/stevearc/oil.nvim)
|
|
||||||
|
|
||||||
https://user-images.githubusercontent.com/506791/209727111-6b4a11f4-634a-4efa-9461-80e9717cea94.mp4
|
https://user-images.githubusercontent.com/506791/209727111-6b4a11f4-634a-4efa-9461-80e9717cea94.mp4
|
||||||
|
|
||||||
## Features
|
This is a maintained fork of [stevearc/oil.nvim](https://github.com/stevearc/oil.nvim)
|
||||||
|
with cherry-picked upstream PRs and original bug fixes that haven't landed
|
||||||
|
upstream yet.
|
||||||
|
|
||||||
- Edit directory listings as normal buffers — mutations are derived by diffing
|
<details>
|
||||||
- Cross-directory move, copy, and rename across any adapter
|
<summary>Changes from upstream</summary>
|
||||||
- Adapters for local filesystem, SSH, S3, and OS trash
|
|
||||||
- File preview in split or floating window
|
### PRs
|
||||||
- Configurable columns (icon, size, permissions, timestamps)
|
|
||||||
- Executable file highlighting and filetype-aware icons
|
Upstream PRs cherry-picked or adapted into this fork.
|
||||||
- Floating window and split layouts
|
|
||||||
|
| PR | Description | Commit |
|
||||||
|
|---|---|---|
|
||||||
|
| [#495](https://github.com/stevearc/oil.nvim/pull/495) | Cancel visual/operator-pending mode on close instead of closing buffer | [`16f3d7b`](https://github.com/barrettruth/oil.nvim/commit/16f3d7b) |
|
||||||
|
| [#537](https://github.com/stevearc/oil.nvim/pull/537) | Configurable file and directory creation permissions (`new_file_mode`, `new_dir_mode`) | [`c6b4a7a`](https://github.com/barrettruth/oil.nvim/commit/c6b4a7a) |
|
||||||
|
| [#578](https://github.com/stevearc/oil.nvim/issues/578) | Recipe to disable hidden file dimming by relinking `Oil*Hidden` groups | [`38db6cf`](https://github.com/barrettruth/oil.nvim/commit/38db6cf) |
|
||||||
|
| [#618](https://github.com/stevearc/oil.nvim/pull/618) | Opt-in filetype detection for icons via `use_slow_filetype_detection` | [`ded1725`](https://github.com/barrettruth/oil.nvim/commit/ded1725) |
|
||||||
|
| [#644](https://github.com/stevearc/oil.nvim/pull/644) | Pass full entry to `is_hidden_file` and `is_always_hidden` callbacks | [`4ab4765`](https://github.com/barrettruth/oil.nvim/commit/4ab4765) |
|
||||||
|
| [#645](https://github.com/stevearc/oil.nvim/pull/645) | Add `close_float` action (close only floating oil windows) | [`f6bcdda`](https://github.com/barrettruth/oil.nvim/commit/f6bcdda) |
|
||||||
|
| [#690](https://github.com/stevearc/oil.nvim/pull/690) | Add `OilFileIcon` highlight group as fallback for unrecognized icons | [`ce64ae1`](https://github.com/barrettruth/oil.nvim/commit/ce64ae1) |
|
||||||
|
| [#697](https://github.com/stevearc/oil.nvim/pull/697) | Recipe for custom file extension column with sorting | [`dcb3a08`](https://github.com/barrettruth/oil.nvim/commit/dcb3a08) |
|
||||||
|
| [#698](https://github.com/stevearc/oil.nvim/pull/698) | Executable file highlighting (`OilExecutable`, `OilExecutableHidden`) | [`41556ec`](https://github.com/barrettruth/oil.nvim/commit/41556ec), [`85ed9b8`](https://github.com/barrettruth/oil.nvim/commit/85ed9b8) |
|
||||||
|
| [#717](https://github.com/stevearc/oil.nvim/pull/717) | Add malewicz1337/oil-git.nvim to third-party extensions | [`582d9fc`](https://github.com/barrettruth/oil.nvim/commit/582d9fc) |
|
||||||
|
| [#720](https://github.com/stevearc/oil.nvim/pull/720) | Gate `BufAdd` autocmd behind `default_file_explorer` check | [`2228f80`](https://github.com/barrettruth/oil.nvim/commit/2228f80) |
|
||||||
|
| [#722](https://github.com/stevearc/oil.nvim/pull/722) | Fix dead freedesktop trash specification URL | [`b92ecb0`](https://github.com/barrettruth/oil.nvim/commit/b92ecb0) |
|
||||||
|
| [#723](https://github.com/stevearc/oil.nvim/pull/723) | Emit `OilReadPost` user event after every buffer render | [`29239d5`](https://github.com/barrettruth/oil.nvim/commit/29239d5) |
|
||||||
|
| [#725](https://github.com/stevearc/oil.nvim/pull/725) | Normalize keymap keys before config merge (`<c-t>` = `<C-t>`) | [`723145c`](https://github.com/barrettruth/oil.nvim/commit/723145c) |
|
||||||
|
| [#727](https://github.com/stevearc/oil.nvim/pull/727) | Clarify `get_current_dir` nil return and add Telescope recipe | [`eed6697`](https://github.com/barrettruth/oil.nvim/commit/eed6697) |
|
||||||
|
|
||||||
|
### Issues
|
||||||
|
|
||||||
|
Upstream issues triaged against this fork.
|
||||||
|
|
||||||
|
| Issue | Status | Resolution |
|
||||||
|
|---|---|---|
|
||||||
|
| [#446](https://github.com/stevearc/oil.nvim/issues/446) | resolved | Executable highlighting — implemented by PR [#698](https://github.com/stevearc/oil.nvim/pull/698) |
|
||||||
|
| [#483](https://github.com/stevearc/oil.nvim/issues/483) | not actionable | Spell downloads depend on netrw — fixed in neovim ([neovim#34940](https://github.com/neovim/neovim/pull/34940)) |
|
||||||
|
| [#492](https://github.com/stevearc/oil.nvim/issues/492) | not actionable | Question — j/k remapping, answered in comments |
|
||||||
|
| [#533](https://github.com/stevearc/oil.nvim/issues/533) | not actionable | `constrain_cursor` — needs repro from reporter |
|
||||||
|
| [#587](https://github.com/stevearc/oil.nvim/issues/587) | not actionable | Alt+h keymap — user config issue |
|
||||||
|
| [#623](https://github.com/stevearc/oil.nvim/issues/623) | not actionable | bufferline.nvim interaction — cross-plugin issue |
|
||||||
|
| [#624](https://github.com/stevearc/oil.nvim/issues/624) | not actionable | Mutation-in-progress race — no reliable repro |
|
||||||
|
| [#632](https://github.com/stevearc/oil.nvim/issues/632) | fixed | Preview + move = copy — [`fe16993`](https://github.com/barrettruth/oil.nvim/commit/fe16993) |
|
||||||
|
| [#642](https://github.com/stevearc/oil.nvim/issues/642) | fixed | W10 warning under `nvim -R` — [`ca834cf`](https://github.com/barrettruth/oil.nvim/commit/ca834cf) |
|
||||||
|
| [#664](https://github.com/stevearc/oil.nvim/issues/664) | not actionable | Extra buffer on session reload — no repro |
|
||||||
|
| [#670](https://github.com/stevearc/oil.nvim/issues/670) | fixed | Multi-directory cmdline — [`70861e5`](https://github.com/barrettruth/oil.nvim/commit/70861e5) |
|
||||||
|
| [#673](https://github.com/stevearc/oil.nvim/issues/673) | fixed | Symlink newlines crash — [`9110a1a`](https://github.com/barrettruth/oil.nvim/commit/9110a1a) |
|
||||||
|
| [#679](https://github.com/stevearc/oil.nvim/issues/679) | resolved | Executable file sign — implemented by PR [#698](https://github.com/stevearc/oil.nvim/pull/698) |
|
||||||
|
| [#692](https://github.com/stevearc/oil.nvim/issues/692) | resolved | Keymap normalization — fixed by PR [#725](https://github.com/stevearc/oil.nvim/pull/725) |
|
||||||
|
| [#710](https://github.com/stevearc/oil.nvim/issues/710) | fixed | buftype empty on BufEnter — [`01b860e`](https://github.com/barrettruth/oil.nvim/commit/01b860e) |
|
||||||
|
| [#714](https://github.com/stevearc/oil.nvim/issues/714) | not actionable | Support question — already answered |
|
||||||
|
| [#719](https://github.com/stevearc/oil.nvim/issues/719) | not actionable | Neovim crash on node_modules delete — libuv/neovim bug |
|
||||||
|
| [#726](https://github.com/stevearc/oil.nvim/issues/726) | not actionable | Meta discussion/roadmap |
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- Neovim 0.10+
|
Neovim 0.8+ and optionally [mini.icons](https://github.com/nvim-mini/mini.nvim/blob/main/readmes/mini-icons.md) or [nvim-web-devicons](https://github.com/nvim-tree/nvim-web-devicons) for file icons.
|
||||||
- (Optionally) any of the following icon providers:
|
|
||||||
- [mini.icons](https://github.com/nvim-mini/mini.nvim/blob/main/readmes/mini-icons.md)
|
|
||||||
- [nvim-web-devicons](https://github.com/nvim-tree/nvim-web-devicons)
|
|
||||||
- [nonicons.nvim](https://github.com/barrettruth/nonicons.nvim)
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Install with your package manager of choice or via
|
Install with your favorite package manager or with luarocks:
|
||||||
[luarocks](https://luarocks.org/modules/barrettruth/canola.nvim):
|
|
||||||
|
|
||||||
```
|
```console
|
||||||
luarocks install canola.nvim
|
luarocks install oil.nvim
|
||||||
```
|
```
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
```vim
|
```vim
|
||||||
:help canola.nvim
|
:help oil.nvim
|
||||||
```
|
```
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
**Q: How do I migrate from `stevearc/oil.nvim`?**
|
**Q: How do I migrate from `stevearc/oil.nvim` to `barrettruth/oil.nvim`?**
|
||||||
|
|
||||||
Change the plugin source and replace `setup()` with `vim.g.canola` in `init`.
|
Replace your `setup()` call with a `vim.g.oil` assignment. For example, with
|
||||||
The configuration table is identical — only the entry point changes. For
|
[lazy.nvim](https://github.com/folke/lazy.nvim):
|
||||||
example, with [lazy.nvim](https://github.com/folke/lazy.nvim):
|
|
||||||
|
|
||||||
Before (`stevearc/oil.nvim`):
|
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
|
-- before
|
||||||
{
|
{
|
||||||
'stevearc/oil.nvim',
|
'stevearc/oil.nvim',
|
||||||
opts = { ... },
|
|
||||||
config = function(_, opts)
|
config = function(_, opts)
|
||||||
require('oil').setup(opts)
|
require('oil').setup(opts)
|
||||||
end,
|
end,
|
||||||
|
opts = {
|
||||||
|
...
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
|
||||||
|
|
||||||
After (`barrettruth/canola.nvim`):
|
-- after
|
||||||
|
|
||||||
```lua
|
|
||||||
{
|
{
|
||||||
'barrettruth/canola.nvim',
|
'barrettruth/oil.nvim',
|
||||||
opts = { ... },
|
init = function()
|
||||||
config = function(_, opts)
|
vim.g.oil = {
|
||||||
require('canola').setup(opts)
|
columns = { "icon", "size" },
|
||||||
|
delete_to_trash = true,
|
||||||
|
}
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`init` runs before the plugin loads; `config` runs after. oil.nvim reads
|
|
||||||
`vim.g.canola` at load time, so `init` is the correct hook. Do not use `config`,
|
|
||||||
`opts`, or `lazy` — oil.nvim loads itself when you open a directory.
|
|
||||||
|
|
||||||
**Q: Why "canola"?**
|
|
||||||
|
|
||||||
Canola oil! But...
|
|
||||||
|
|
||||||
**Q: Why "oil"?**
|
**Q: Why "oil"?**
|
||||||
|
|
||||||
From the [vim-vinegar](https://github.com/tpope/vim-vinegar) README, a quote by
|
**A:** From the [vim-vinegar](https://github.com/tpope/vim-vinegar) README, a quote by Drew Neil:
|
||||||
Drew Neil:
|
|
||||||
|
|
||||||
> Split windows and the project drawer go together like oil and vinegar
|
> Split windows and the project drawer go together like oil and vinegar
|
||||||
|
|
||||||
|
Vinegar was taken. Let's be oil.
|
||||||
|
Plus, I think it's pretty slick ;)
|
||||||
|
|
||||||
|
**Q: Why would I want to use oil vs any other plugin?**
|
||||||
|
|
||||||
|
**A:**
|
||||||
|
|
||||||
|
- 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 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
|
||||||
|
|
||||||
|
**Q: Can oil display files as a tree view?**
|
||||||
|
|
||||||
|
**A:** No. A tree view would require a completely different methodology, necessitating a complete rewrite.
|
||||||
|
|
||||||
**Q: What are some alternatives?**
|
**Q: What are some alternatives?**
|
||||||
|
|
||||||
- [stevearc/oil.nvim](https://github.com/stevearc/oil.nvim): the original
|
**A:**
|
||||||
- [mini.files](https://github.com/nvim-mini/mini.nvim/blob/main/readmes/mini-files.md):
|
|
||||||
cross-directory filesystem-as-buffer with a column view
|
|
||||||
- [vim-vinegar](https://github.com/tpope/vim-vinegar): the granddaddy of
|
|
||||||
single-directory file browsing
|
|
||||||
- [dirbuf.nvim](https://github.com/elihunter173/dirbuf.nvim): filesystem as
|
|
||||||
buffer without cross-directory edits
|
|
||||||
- [lir.nvim](https://github.com/tamago324/lir.nvim): vim-vinegar style with
|
|
||||||
Neovim integration
|
|
||||||
- [vim-dirvish](https://github.com/justinmk/vim-dirvish): stable, simple
|
|
||||||
directory browser
|
|
||||||
|
|
||||||
## Acknowledgements
|
- [the original](https://github.com/stevearc/oil.nvim): the lesser-maintained but
|
||||||
|
official `oil.nvim`
|
||||||
- [stevearc](https://github.com/stevearc):
|
- [mini.files](https://github.com/nvim-mini/mini.nvim/blob/main/readmes/mini-files.md): Also supports cross-directory filesystem-as-buffer edits with a column view.
|
||||||
[oil.nvim](https://github.com/stevearc/oil.nvim)
|
- [vim-vinegar](https://github.com/tpope/vim-vinegar): The granddaddy of single-directory file browsing.
|
||||||
|
- [dirbuf.nvim](https://github.com/elihunter173/dirbuf.nvim): Edit filesystem like a buffer, but no cross-directory edits.
|
||||||
|
- [lir.nvim](https://github.com/tamago324/lir.nvim): Similar to vim-vinegar with better Neovim integration.
|
||||||
|
- [vim-dirvish](https://github.com/justinmk/vim-dirvish): Stable, simple directory browser.
|
||||||
|
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
rockspec_format = '3.0'
|
|
||||||
package = 'canola.nvim'
|
|
||||||
version = 'scm-1'
|
|
||||||
|
|
||||||
source = {
|
|
||||||
url = 'git+https://github.com/barrettruth/canola.nvim.git',
|
|
||||||
}
|
|
||||||
|
|
||||||
description = {
|
|
||||||
summary = 'Neovim file explorer: edit your filesystem like a buffer',
|
|
||||||
homepage = 'https://github.com/barrettruth/canola.nvim',
|
|
||||||
license = 'MIT',
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies = {
|
|
||||||
'lua >= 5.1',
|
|
||||||
}
|
|
||||||
|
|
||||||
test_dependencies = {
|
|
||||||
'nlua',
|
|
||||||
'busted >= 2.1.1',
|
|
||||||
}
|
|
||||||
|
|
||||||
test = {
|
|
||||||
type = 'busted',
|
|
||||||
}
|
|
||||||
|
|
||||||
build = {
|
|
||||||
type = 'builtin',
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load diff
163
doc/upstream.md
163
doc/upstream.md
|
|
@ -1,163 +0,0 @@
|
||||||
# Upstream Tracker
|
|
||||||
|
|
||||||
Triage of [stevearc/oil.nvim](https://github.com/stevearc/oil.nvim) PRs and
|
|
||||||
issues against this fork.
|
|
||||||
|
|
||||||
## Upstream PRs
|
|
||||||
|
|
||||||
| PR | Description | Status |
|
|
||||||
|----|-------------|--------|
|
|
||||||
| [#495](https://github.com/stevearc/oil.nvim/pull/495) | Cancel visual/operator-pending mode on close | cherry-picked |
|
|
||||||
| [#537](https://github.com/stevearc/oil.nvim/pull/537) | Configurable file/directory creation permissions | cherry-picked |
|
|
||||||
| [#618](https://github.com/stevearc/oil.nvim/pull/618) | Opt-in filetype detection for icons | cherry-picked |
|
|
||||||
| [#644](https://github.com/stevearc/oil.nvim/pull/644) | Pass entry to `is_hidden_file`/`is_always_hidden` | cherry-picked |
|
|
||||||
| [#697](https://github.com/stevearc/oil.nvim/pull/697) | Recipe for file extension column | cherry-picked |
|
|
||||||
| [#698](https://github.com/stevearc/oil.nvim/pull/698) | Executable file highlighting | cherry-picked |
|
|
||||||
| [#717](https://github.com/stevearc/oil.nvim/pull/717) | Add oil-git.nvim to extensions | cherry-picked |
|
|
||||||
| [#720](https://github.com/stevearc/oil.nvim/pull/720) | Gate `BufAdd` autocmd behind config check | cherry-picked |
|
|
||||||
| [#722](https://github.com/stevearc/oil.nvim/pull/722) | Fix freedesktop trash URL | cherry-picked |
|
|
||||||
| [#723](https://github.com/stevearc/oil.nvim/pull/723) | Emit `OilReadPost` event after render | cherry-picked |
|
|
||||||
| [#725](https://github.com/stevearc/oil.nvim/pull/725) | Normalize keymap keys before config merge | cherry-picked |
|
|
||||||
| [#727](https://github.com/stevearc/oil.nvim/pull/727) | Clarify `get_current_dir` nil + Telescope recipe | cherry-picked |
|
|
||||||
| [#739](https://github.com/stevearc/oil.nvim/pull/739) | macOS FreeDesktop trash recipe | cherry-picked |
|
|
||||||
| [#488](https://github.com/stevearc/oil.nvim/pull/488) | Parent directory in a split | not actionable — empty PR |
|
|
||||||
| [#493](https://github.com/stevearc/oil.nvim/pull/493) | UNC paths on Windows | not actionable — superseded by [#686](https://github.com/stevearc/oil.nvim/pull/686) |
|
|
||||||
| [#686](https://github.com/stevearc/oil.nvim/pull/686) | Windows path conversion fix | not actionable — Windows-only |
|
|
||||||
| [#735](https://github.com/stevearc/oil.nvim/pull/735) | gX opens external program with selection | not actionable — hardcoded Linux-only, incomplete |
|
|
||||||
| [#591](https://github.com/stevearc/oil.nvim/pull/591) | release-please changelog | not applicable |
|
|
||||||
| [#667](https://github.com/stevearc/oil.nvim/pull/667) | Virtual text columns + headers | deferred — WIP, conflicting |
|
|
||||||
| [#708](https://github.com/stevearc/oil.nvim/pull/708) | Move file into new dir by renaming | deferred — needs rewrite |
|
|
||||||
| [#721](https://github.com/stevearc/oil.nvim/pull/721) | `create_hook` to populate file contents | deferred — fixing via autocmd event |
|
|
||||||
| [#728](https://github.com/stevearc/oil.nvim/pull/728) | `open_split` for opening oil in a split | deferred — tracked as [#2](https://github.com/barrettruth/canola.nvim/issues/2) |
|
|
||||||
|
|
||||||
## Issues — fixed (original)
|
|
||||||
|
|
||||||
Issues fixed in this fork that remain open upstream.
|
|
||||||
|
|
||||||
| Issue | Description | PR |
|
|
||||||
|-------|-------------|----|
|
|
||||||
| [#213](https://github.com/stevearc/oil.nvim/issues/213) | Disable preview for large files | [#85](https://github.com/barrettruth/canola.nvim/pull/85) |
|
|
||||||
| [#302](https://github.com/stevearc/oil.nvim/issues/302) | `buflisted=true` after jumplist nav | [#71](https://github.com/barrettruth/canola.nvim/pull/71) |
|
|
||||||
| [#363](https://github.com/stevearc/oil.nvim/issues/363) | `prompt_save_on_select_new_entry` wrong prompt | — |
|
|
||||||
| [#392](https://github.com/stevearc/oil.nvim/issues/392) | Option to skip delete prompt | — |
|
|
||||||
| [#393](https://github.com/stevearc/oil.nvim/issues/393) | Auto-save on select | — |
|
|
||||||
| [#473](https://github.com/stevearc/oil.nvim/issues/473) | Show hidden when dir is all-hidden | [#85](https://github.com/barrettruth/canola.nvim/pull/85) |
|
|
||||||
| [#486](https://github.com/stevearc/oil.nvim/issues/486) | Directory sizes show misleading 4.1k | [#87](https://github.com/barrettruth/canola.nvim/pull/87) |
|
|
||||||
| [#578](https://github.com/stevearc/oil.nvim/issues/578) | Hidden file dimming recipe | — |
|
|
||||||
| [#612](https://github.com/stevearc/oil.nvim/issues/612) | Delete buffers on file delete | — |
|
|
||||||
| [#615](https://github.com/stevearc/oil.nvim/issues/615) | Cursor at name column on o/O | [#72](https://github.com/barrettruth/canola.nvim/pull/72) |
|
|
||||||
| [#621](https://github.com/stevearc/oil.nvim/issues/621) | `toggle()` for regular windows | [#88](https://github.com/barrettruth/canola.nvim/pull/88) |
|
|
||||||
| [#632](https://github.com/stevearc/oil.nvim/issues/632) | Preview + move = copy | [#12](https://github.com/barrettruth/canola.nvim/pull/12) |
|
|
||||||
| [#642](https://github.com/stevearc/oil.nvim/issues/642) | W10 warning under `nvim -R` | — |
|
|
||||||
| [#645](https://github.com/stevearc/oil.nvim/issues/645) | `close_float` action | — |
|
|
||||||
| [#650](https://github.com/stevearc/oil.nvim/issues/650) | LSP `workspace.fileOperations` events | — |
|
|
||||||
| [#670](https://github.com/stevearc/oil.nvim/issues/670) | Multi-directory cmdline args ignored | [#11](https://github.com/barrettruth/canola.nvim/pull/11) |
|
|
||||||
| [#673](https://github.com/stevearc/oil.nvim/issues/673) | Symlink newlines crash | — |
|
|
||||||
| [#683](https://github.com/stevearc/oil.nvim/issues/683) | Path not shown in floating mode | — |
|
|
||||||
| [#690](https://github.com/stevearc/oil.nvim/issues/690) | `OilFileIcon` highlight group | — |
|
|
||||||
| [#710](https://github.com/stevearc/oil.nvim/issues/710) | buftype empty on BufEnter | [#10](https://github.com/barrettruth/canola.nvim/pull/10) |
|
|
||||||
|
|
||||||
## Issues — resolved (cherry-pick)
|
|
||||||
|
|
||||||
Issues addressed by cherry-picking upstream PRs.
|
|
||||||
|
|
||||||
| Issue | Description | Upstream PR |
|
|
||||||
|-------|-------------|-------------|
|
|
||||||
| [#446](https://github.com/stevearc/oil.nvim/issues/446) | Executable highlighting | [#698](https://github.com/stevearc/oil.nvim/pull/698) |
|
|
||||||
| [#679](https://github.com/stevearc/oil.nvim/issues/679) | Executable file sign | [#698](https://github.com/stevearc/oil.nvim/pull/698) |
|
|
||||||
| [#682](https://github.com/stevearc/oil.nvim/issues/682) | `get_current_dir()` nil | [#727](https://github.com/stevearc/oil.nvim/pull/727) |
|
|
||||||
| [#692](https://github.com/stevearc/oil.nvim/issues/692) | Keymap normalization | [#725](https://github.com/stevearc/oil.nvim/pull/725) |
|
|
||||||
|
|
||||||
## Issues — open
|
|
||||||
|
|
||||||
| Issue | Description |
|
|
||||||
|-------|-------------|
|
|
||||||
| [#85](https://github.com/stevearc/oil.nvim/issues/85) | Git status column |
|
|
||||||
| [#95](https://github.com/stevearc/oil.nvim/issues/95) | Undo after renaming files |
|
|
||||||
| [#117](https://github.com/stevearc/oil.nvim/issues/117) | Move file into new dir via slash in name |
|
|
||||||
| [#156](https://github.com/stevearc/oil.nvim/issues/156) | Paste path of files into oil buffer |
|
|
||||||
| [#200](https://github.com/stevearc/oil.nvim/issues/200) | Highlights not working when opening a file |
|
|
||||||
| [#207](https://github.com/stevearc/oil.nvim/issues/207) | Suppress "no longer available" message |
|
|
||||||
| [#210](https://github.com/stevearc/oil.nvim/issues/210) | FTP support |
|
|
||||||
| [#226](https://github.com/stevearc/oil.nvim/issues/226) | K8s/Docker adapter |
|
|
||||||
| [#232](https://github.com/stevearc/oil.nvim/issues/232) | Cannot close last window |
|
|
||||||
| [#254](https://github.com/stevearc/oil.nvim/issues/254) | Buffer modified highlight group |
|
|
||||||
| [#263](https://github.com/stevearc/oil.nvim/issues/263) | Diff mode |
|
|
||||||
| [#276](https://github.com/stevearc/oil.nvim/issues/276) | Archives manipulation |
|
|
||||||
| [#280](https://github.com/stevearc/oil.nvim/issues/280) | vim-projectionist support |
|
|
||||||
| [#289](https://github.com/stevearc/oil.nvim/issues/289) | Show absolute path toggle |
|
|
||||||
| [#294](https://github.com/stevearc/oil.nvim/issues/294) | Can't handle emojis in filenames |
|
|
||||||
| [#298](https://github.com/stevearc/oil.nvim/issues/298) | Open float on neovim directory startup |
|
|
||||||
| [#303](https://github.com/stevearc/oil.nvim/issues/303) | Preview in float window mode |
|
|
||||||
| [#325](https://github.com/stevearc/oil.nvim/issues/325) | oil-ssh error from command line |
|
|
||||||
| [#332](https://github.com/stevearc/oil.nvim/issues/332) | Buffer not fixed to floating window |
|
|
||||||
| [#335](https://github.com/stevearc/oil.nvim/issues/335) | Disable editing outside root dir |
|
|
||||||
| [#349](https://github.com/stevearc/oil.nvim/issues/349) | Parent directory as column/vsplit |
|
|
||||||
| [#351](https://github.com/stevearc/oil.nvim/issues/351) | Paste deleted file from register |
|
|
||||||
| [#359](https://github.com/stevearc/oil.nvim/issues/359) | Parse error on filenames differing by space |
|
|
||||||
| [#360](https://github.com/stevearc/oil.nvim/issues/360) | Pick window to open file into |
|
|
||||||
| [#371](https://github.com/stevearc/oil.nvim/issues/371) | Constrain cursor in insert mode |
|
|
||||||
| [#373](https://github.com/stevearc/oil.nvim/issues/373) | Dir from quickfix with bqf/trouble broken |
|
|
||||||
| [#375](https://github.com/stevearc/oil.nvim/issues/375) | Highlights for file types and permissions |
|
|
||||||
| [#382](https://github.com/stevearc/oil.nvim/issues/382) | Relative path in window title |
|
|
||||||
| [#396](https://github.com/stevearc/oil.nvim/issues/396) | Customize preview content |
|
|
||||||
| [#399](https://github.com/stevearc/oil.nvim/issues/399) | Open file without closing Oil |
|
|
||||||
| [#416](https://github.com/stevearc/oil.nvim/issues/416) | Cannot remap key to open split |
|
|
||||||
| [#431](https://github.com/stevearc/oil.nvim/issues/431) | More SSH adapter documentation |
|
|
||||||
| [#435](https://github.com/stevearc/oil.nvim/issues/435) | Error previewing with semantic tokens LSP |
|
|
||||||
| [#436](https://github.com/stevearc/oil.nvim/issues/436) | Owner and group columns |
|
|
||||||
| [#444](https://github.com/stevearc/oil.nvim/issues/444) | Opening behaviour customization |
|
|
||||||
| [#449](https://github.com/stevearc/oil.nvim/issues/449) | Renaming TypeScript files stopped working |
|
|
||||||
| [#450](https://github.com/stevearc/oil.nvim/issues/450) | Highlight opened file in directory listing |
|
|
||||||
| [#457](https://github.com/stevearc/oil.nvim/issues/457) | Custom column API |
|
|
||||||
| [#466](https://github.com/stevearc/oil.nvim/issues/466) | Select into window on right |
|
|
||||||
| [#479](https://github.com/stevearc/oil.nvim/issues/479) | Harpoon integration recipe |
|
|
||||||
| [#521](https://github.com/stevearc/oil.nvim/issues/521) | oil-ssh connection issues |
|
|
||||||
| [#525](https://github.com/stevearc/oil.nvim/issues/525) | SSH adapter documentation |
|
|
||||||
| [#570](https://github.com/stevearc/oil.nvim/issues/570) | Improve c0/d0 for renaming |
|
|
||||||
| [#571](https://github.com/stevearc/oil.nvim/issues/571) | Callback before `highlight_filename` |
|
|
||||||
| [#599](https://github.com/stevearc/oil.nvim/issues/599) | user:group display and manipulation |
|
|
||||||
| [#607](https://github.com/stevearc/oil.nvim/issues/607) | Per-host SCP args |
|
|
||||||
| [#609](https://github.com/stevearc/oil.nvim/issues/609) | Cursor placement via Snacks picker |
|
|
||||||
| [#617](https://github.com/stevearc/oil.nvim/issues/617) | Filetype by actual filetype |
|
|
||||||
| [#636](https://github.com/stevearc/oil.nvim/issues/636) | Telescope picker opens in active buffer |
|
|
||||||
| [#637](https://github.com/stevearc/oil.nvim/issues/637) | Inconsistent symlink resolution |
|
|
||||||
| [#641](https://github.com/stevearc/oil.nvim/issues/641) | Flicker on `actions.parent` |
|
|
||||||
| [#646](https://github.com/stevearc/oil.nvim/issues/646) | `get_current_dir` nil on SSH |
|
|
||||||
| [#655](https://github.com/stevearc/oil.nvim/issues/655) | File statistics as virtual text |
|
|
||||||
| [#659](https://github.com/stevearc/oil.nvim/issues/659) | Mark and diff files in buffer |
|
|
||||||
| [#665](https://github.com/stevearc/oil.nvim/issues/665) | Hot load preview fast-scratch buffers |
|
|
||||||
| [#668](https://github.com/stevearc/oil.nvim/issues/668) | Custom yes/no confirmation |
|
|
||||||
| [#671](https://github.com/stevearc/oil.nvim/issues/671) | Yanking between nvim instances |
|
|
||||||
| [#675](https://github.com/stevearc/oil.nvim/issues/675) | Move file into folder by renaming |
|
|
||||||
| [#678](https://github.com/stevearc/oil.nvim/issues/678) | `buftype='acwrite'` causes `mksession` to skip oil windows |
|
|
||||||
| [#684](https://github.com/stevearc/oil.nvim/issues/684) | User and group columns |
|
|
||||||
| [#685](https://github.com/stevearc/oil.nvim/issues/685) | Plain directory paths in buffer names |
|
|
||||||
| [#699](https://github.com/stevearc/oil.nvim/issues/699) | `select` blocks UI with slow FileType autocmd |
|
|
||||||
| [#707](https://github.com/stevearc/oil.nvim/issues/707) | Move file/dir into new dir by renaming |
|
|
||||||
| [#736](https://github.com/stevearc/oil.nvim/issues/736) | Make icons virtual text |
|
|
||||||
| [#738](https://github.com/stevearc/oil.nvim/issues/738) | Allow changing mtime/atime via time column |
|
|
||||||
|
|
||||||
## Issues — not actionable
|
|
||||||
|
|
||||||
| Issue | Reason |
|
|
||||||
|-------|--------|
|
|
||||||
| [#288](https://github.com/stevearc/oil.nvim/issues/288) | No reliable repro; likely lazy.nvim timing |
|
|
||||||
| [#330](https://github.com/stevearc/oil.nvim/issues/330) | Telescope opens file in oil float — cross-plugin, no repro |
|
|
||||||
| [#362](https://github.com/stevearc/oil.nvim/issues/362) | No minimal repro, old nvim version (0.9.5) |
|
|
||||||
| [#380](https://github.com/stevearc/oil.nvim/issues/380) | Silently overriding `show_hidden` counter to config intent |
|
|
||||||
| [#404](https://github.com/stevearc/oil.nvim/issues/404) | Windows-only |
|
|
||||||
| [#483](https://github.com/stevearc/oil.nvim/issues/483) | Spell downloads depend on netrw — fixed in neovim#34940 |
|
|
||||||
| [#492](https://github.com/stevearc/oil.nvim/issues/492) | j/k remapping question — answered |
|
|
||||||
| [#507](https://github.com/stevearc/oil.nvim/issues/507) | lacasitos.nvim conflict — cross-plugin + Windows-only |
|
|
||||||
| [#531](https://github.com/stevearc/oil.nvim/issues/531) | Windows — incomplete drive letters |
|
|
||||||
| [#533](https://github.com/stevearc/oil.nvim/issues/533) | `constrain_cursor` — needs repro |
|
|
||||||
| [#587](https://github.com/stevearc/oil.nvim/issues/587) | Alt+h keymap — user config issue |
|
|
||||||
| [#623](https://github.com/stevearc/oil.nvim/issues/623) | bufferline.nvim interaction — cross-plugin |
|
|
||||||
| [#624](https://github.com/stevearc/oil.nvim/issues/624) | Mutation race — no reliable repro |
|
|
||||||
| [#625](https://github.com/stevearc/oil.nvim/issues/625) | E19 mark invalid line — intractable without neovim API changes |
|
|
||||||
| [#664](https://github.com/stevearc/oil.nvim/issues/664) | Session reload extra buffer — no repro |
|
|
||||||
| [#676](https://github.com/stevearc/oil.nvim/issues/676) | Windows — path conversion |
|
|
||||||
| [#714](https://github.com/stevearc/oil.nvim/issues/714) | Support question — answered |
|
|
||||||
| [#719](https://github.com/stevearc/oil.nvim/issues/719) | Neovim crash on node_modules — libuv/neovim bug |
|
|
||||||
| [#726](https://github.com/stevearc/oil.nvim/issues/726) | Meta discussion/roadmap |
|
|
||||||
11
flake.nix
11
flake.nix
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
description = "canola.nvim — refined fork of oil.nvim";
|
description = "oil.nvim — Neovim file explorer: edit your filesystem like a buffer";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||||
|
|
@ -13,22 +13,15 @@
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
forEachSystem =
|
forEachSystem = f: nixpkgs.lib.genAttrs (import systems) (system: f nixpkgs.legacyPackages.${system});
|
||||||
f: nixpkgs.lib.genAttrs (import systems) (system: f nixpkgs.legacyPackages.${system});
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
formatter = forEachSystem (pkgs: pkgs.nixfmt-tree);
|
|
||||||
|
|
||||||
devShells = forEachSystem (pkgs: {
|
devShells = forEachSystem (pkgs: {
|
||||||
default = pkgs.mkShell {
|
default = pkgs.mkShell {
|
||||||
packages = [
|
packages = [
|
||||||
pkgs.prettier
|
pkgs.prettier
|
||||||
pkgs.stylua
|
pkgs.stylua
|
||||||
pkgs.selene
|
pkgs.selene
|
||||||
(pkgs.luajit.withPackages (ps: [
|
|
||||||
ps.busted
|
|
||||||
ps.nlua
|
|
||||||
]))
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
local fs = require('canola.fs')
|
|
||||||
|
|
||||||
if fs.is_mac then
|
|
||||||
return require('canola.adapters.trash.mac')
|
|
||||||
elseif fs.is_windows then
|
|
||||||
return require('canola.adapters.trash.windows')
|
|
||||||
else
|
|
||||||
return require('canola.adapters.trash.freedesktop')
|
|
||||||
end
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
local M = {}
|
|
||||||
|
|
||||||
---@param path string
|
|
||||||
---@return string
|
|
||||||
M.parent = function(path)
|
|
||||||
if path == '/' then
|
|
||||||
return '/'
|
|
||||||
elseif path == '' then
|
|
||||||
return ''
|
|
||||||
elseif vim.endswith(path, '/') then
|
|
||||||
return path:match('^(.*/)[^/]*/$') or ''
|
|
||||||
else
|
|
||||||
return path:match('^(.*/)[^/]*$') or ''
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param path string
|
|
||||||
---@return nil|string
|
|
||||||
M.basename = function(path)
|
|
||||||
if path == '/' or path == '' then
|
|
||||||
return
|
|
||||||
elseif vim.endswith(path, '/') then
|
|
||||||
return path:match('^.*/([^/]*)/$')
|
|
||||||
else
|
|
||||||
return path:match('^.*/([^/]*)$')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
|
|
@ -1,119 +1,119 @@
|
||||||
local canola = require('canola')
|
local oil = require("oil")
|
||||||
local util = require('canola.util')
|
local util = require("oil.util")
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
M.show_help = {
|
M.show_help = {
|
||||||
callback = function()
|
callback = function()
|
||||||
local config = require('canola.config')
|
local config = require("oil.config")
|
||||||
require('canola.keymap_util').show_help(config.keymaps)
|
require("oil.keymap_util").show_help(config.keymaps)
|
||||||
end,
|
end,
|
||||||
desc = 'Show default keymaps',
|
desc = "Show default keymaps",
|
||||||
}
|
}
|
||||||
|
|
||||||
M.select = {
|
M.select = {
|
||||||
desc = 'Open the entry under the cursor',
|
desc = "Open the entry under the cursor",
|
||||||
callback = function(opts)
|
callback = function(opts)
|
||||||
opts = opts or {}
|
opts = opts or {}
|
||||||
local callback = opts.callback
|
local callback = opts.callback
|
||||||
opts.callback = nil
|
opts.callback = nil
|
||||||
canola.select(opts, callback)
|
oil.select(opts, callback)
|
||||||
end,
|
end,
|
||||||
parameters = {
|
parameters = {
|
||||||
vertical = {
|
vertical = {
|
||||||
type = 'boolean',
|
type = "boolean",
|
||||||
desc = 'Open the buffer in a vertical split',
|
desc = "Open the buffer in a vertical split",
|
||||||
},
|
},
|
||||||
horizontal = {
|
horizontal = {
|
||||||
type = 'boolean',
|
type = "boolean",
|
||||||
desc = 'Open the buffer in a horizontal split',
|
desc = "Open the buffer in a horizontal split",
|
||||||
},
|
},
|
||||||
split = {
|
split = {
|
||||||
type = '"aboveleft"|"belowright"|"topleft"|"botright"',
|
type = '"aboveleft"|"belowright"|"topleft"|"botright"',
|
||||||
desc = 'Split modifier',
|
desc = "Split modifier",
|
||||||
},
|
},
|
||||||
tab = {
|
tab = {
|
||||||
type = 'boolean',
|
type = "boolean",
|
||||||
desc = 'Open the buffer in a new tab',
|
desc = "Open the buffer in a new tab",
|
||||||
},
|
},
|
||||||
close = {
|
close = {
|
||||||
type = 'boolean',
|
type = "boolean",
|
||||||
desc = 'Close the original canola buffer once selection is made',
|
desc = "Close the original oil buffer once selection is made",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
M.select_vsplit = {
|
M.select_vsplit = {
|
||||||
desc = 'Open the entry under the cursor in a vertical split',
|
desc = "Open the entry under the cursor in a vertical split",
|
||||||
deprecated = true,
|
deprecated = true,
|
||||||
callback = function()
|
callback = function()
|
||||||
canola.select({ vertical = true })
|
oil.select({ vertical = true })
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
M.select_split = {
|
M.select_split = {
|
||||||
desc = 'Open the entry under the cursor in a horizontal split',
|
desc = "Open the entry under the cursor in a horizontal split",
|
||||||
deprecated = true,
|
deprecated = true,
|
||||||
callback = function()
|
callback = function()
|
||||||
canola.select({ horizontal = true })
|
oil.select({ horizontal = true })
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
M.select_tab = {
|
M.select_tab = {
|
||||||
desc = 'Open the entry under the cursor in a new tab',
|
desc = "Open the entry under the cursor in a new tab",
|
||||||
deprecated = true,
|
deprecated = true,
|
||||||
callback = function()
|
callback = function()
|
||||||
canola.select({ tab = true })
|
oil.select({ tab = true })
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
M.preview = {
|
M.preview = {
|
||||||
desc = 'Open the entry under the cursor in a preview window, or close the preview window if already open',
|
desc = "Open the entry under the cursor in a preview window, or close the preview window if already open",
|
||||||
parameters = {
|
parameters = {
|
||||||
vertical = {
|
vertical = {
|
||||||
type = 'boolean',
|
type = "boolean",
|
||||||
desc = 'Open the buffer in a vertical split',
|
desc = "Open the buffer in a vertical split",
|
||||||
},
|
},
|
||||||
horizontal = {
|
horizontal = {
|
||||||
type = 'boolean',
|
type = "boolean",
|
||||||
desc = 'Open the buffer in a horizontal split',
|
desc = "Open the buffer in a horizontal split",
|
||||||
},
|
},
|
||||||
split = {
|
split = {
|
||||||
type = '"aboveleft"|"belowright"|"topleft"|"botright"',
|
type = '"aboveleft"|"belowright"|"topleft"|"botright"',
|
||||||
desc = 'Split modifier',
|
desc = "Split modifier",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
callback = function(opts)
|
callback = function(opts)
|
||||||
local entry = canola.get_cursor_entry()
|
local entry = oil.get_cursor_entry()
|
||||||
if not entry then
|
if not entry then
|
||||||
vim.notify('Could not find entry under cursor', vim.log.levels.ERROR)
|
vim.notify("Could not find entry under cursor", vim.log.levels.ERROR)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local winid = util.get_preview_win()
|
local winid = util.get_preview_win()
|
||||||
if winid then
|
if winid then
|
||||||
local cur_id = vim.w[winid].canola_entry_id
|
local cur_id = vim.w[winid].oil_entry_id
|
||||||
if entry.id == cur_id then
|
if entry.id == cur_id then
|
||||||
vim.api.nvim_win_close(winid, true)
|
vim.api.nvim_win_close(winid, true)
|
||||||
if util.is_floating_win() then
|
if util.is_floating_win() then
|
||||||
local layout = require('canola.layout')
|
local layout = require("oil.layout")
|
||||||
local win_opts = layout.get_fullscreen_win_opts()
|
local win_opts = layout.get_fullscreen_win_opts()
|
||||||
vim.api.nvim_win_set_config(0, win_opts)
|
vim.api.nvim_win_set_config(0, win_opts)
|
||||||
end
|
end
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
canola.open_preview(opts)
|
oil.open_preview(opts)
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
M.preview_scroll_down = {
|
M.preview_scroll_down = {
|
||||||
desc = 'Scroll down in the preview window',
|
desc = "Scroll down in the preview window",
|
||||||
callback = function()
|
callback = function()
|
||||||
local winid = util.get_preview_win()
|
local winid = util.get_preview_win()
|
||||||
if winid then
|
if winid then
|
||||||
vim.api.nvim_win_call(winid, function()
|
vim.api.nvim_win_call(winid, function()
|
||||||
vim.cmd.normal({
|
vim.cmd.normal({
|
||||||
args = { vim.api.nvim_replace_termcodes('<C-d>', true, true, true) },
|
args = { vim.api.nvim_replace_termcodes("<C-d>", true, true, true) },
|
||||||
bang = true,
|
bang = true,
|
||||||
})
|
})
|
||||||
end)
|
end)
|
||||||
|
|
@ -122,13 +122,13 @@ M.preview_scroll_down = {
|
||||||
}
|
}
|
||||||
|
|
||||||
M.preview_scroll_up = {
|
M.preview_scroll_up = {
|
||||||
desc = 'Scroll up in the preview window',
|
desc = "Scroll up in the preview window",
|
||||||
callback = function()
|
callback = function()
|
||||||
local winid = util.get_preview_win()
|
local winid = util.get_preview_win()
|
||||||
if winid then
|
if winid then
|
||||||
vim.api.nvim_win_call(winid, function()
|
vim.api.nvim_win_call(winid, function()
|
||||||
vim.cmd.normal({
|
vim.cmd.normal({
|
||||||
args = { vim.api.nvim_replace_termcodes('<C-u>', true, true, true) },
|
args = { vim.api.nvim_replace_termcodes("<C-u>", true, true, true) },
|
||||||
bang = true,
|
bang = true,
|
||||||
})
|
})
|
||||||
end)
|
end)
|
||||||
|
|
@ -137,60 +137,60 @@ M.preview_scroll_up = {
|
||||||
}
|
}
|
||||||
|
|
||||||
M.preview_scroll_left = {
|
M.preview_scroll_left = {
|
||||||
desc = 'Scroll left in the preview window',
|
desc = "Scroll left in the preview window",
|
||||||
callback = function()
|
callback = function()
|
||||||
local winid = util.get_preview_win()
|
local winid = util.get_preview_win()
|
||||||
if winid then
|
if winid then
|
||||||
vim.api.nvim_win_call(winid, function()
|
vim.api.nvim_win_call(winid, function()
|
||||||
vim.cmd.normal({ 'zH', bang = true })
|
vim.cmd.normal({ "zH", bang = true })
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
M.preview_scroll_right = {
|
M.preview_scroll_right = {
|
||||||
desc = 'Scroll right in the preview window',
|
desc = "Scroll right in the preview window",
|
||||||
callback = function()
|
callback = function()
|
||||||
local winid = util.get_preview_win()
|
local winid = util.get_preview_win()
|
||||||
if winid then
|
if winid then
|
||||||
vim.api.nvim_win_call(winid, function()
|
vim.api.nvim_win_call(winid, function()
|
||||||
vim.cmd.normal({ 'zL', bang = true })
|
vim.cmd.normal({ "zL", bang = true })
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
M.parent = {
|
M.parent = {
|
||||||
desc = 'Navigate to the parent path',
|
desc = "Navigate to the parent path",
|
||||||
callback = canola.open,
|
callback = oil.open,
|
||||||
}
|
}
|
||||||
|
|
||||||
M.close = {
|
M.close = {
|
||||||
desc = 'Close canola and restore original buffer',
|
desc = "Close oil and restore original buffer",
|
||||||
callback = function(opts)
|
callback = function(opts)
|
||||||
opts = opts or {}
|
opts = opts or {}
|
||||||
canola.close(opts)
|
oil.close(opts)
|
||||||
end,
|
end,
|
||||||
parameters = {
|
parameters = {
|
||||||
exit_if_last_buf = {
|
exit_if_last_buf = {
|
||||||
type = 'boolean',
|
type = "boolean",
|
||||||
desc = 'Exit vim if canola is closed as the last buffer',
|
desc = "Exit vim if oil is closed as the last buffer",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
M.close_float = {
|
M.close_float = {
|
||||||
desc = 'Close canola if the window is floating, otherwise do nothing',
|
desc = "Close oil if the window is floating, otherwise do nothing",
|
||||||
callback = function(opts)
|
callback = function(opts)
|
||||||
if vim.w.is_canola_win then
|
if vim.w.is_oil_win then
|
||||||
opts = opts or {}
|
opts = opts or {}
|
||||||
canola.close(opts)
|
oil.close(opts)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
parameters = {
|
parameters = {
|
||||||
exit_if_last_buf = {
|
exit_if_last_buf = {
|
||||||
type = 'boolean',
|
type = "boolean",
|
||||||
desc = 'Exit vim if canola is closed as the last buffer',
|
desc = "Exit vim if oil is closed as the last buffer",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -198,97 +198,97 @@ M.close_float = {
|
||||||
---@param cmd string
|
---@param cmd string
|
||||||
---@param silent? boolean
|
---@param silent? boolean
|
||||||
local function cd(cmd, silent)
|
local function cd(cmd, silent)
|
||||||
local dir = canola.get_current_dir()
|
local dir = oil.get_current_dir()
|
||||||
if dir then
|
if dir then
|
||||||
vim.cmd({ cmd = cmd, args = { dir } })
|
vim.cmd({ cmd = cmd, args = { dir } })
|
||||||
if not silent then
|
if not silent then
|
||||||
vim.notify(string.format('CWD: %s', dir), vim.log.levels.INFO)
|
vim.notify(string.format("CWD: %s", dir), vim.log.levels.INFO)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
vim.notify('Cannot :cd; not in a directory', vim.log.levels.WARN)
|
vim.notify("Cannot :cd; not in a directory", vim.log.levels.WARN)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
M.cd = {
|
M.cd = {
|
||||||
desc = ':cd to the current canola directory',
|
desc = ":cd to the current oil directory",
|
||||||
callback = function(opts)
|
callback = function(opts)
|
||||||
opts = opts or {}
|
opts = opts or {}
|
||||||
local cmd = 'cd'
|
local cmd = "cd"
|
||||||
if opts.scope == 'tab' then
|
if opts.scope == "tab" then
|
||||||
cmd = 'tcd'
|
cmd = "tcd"
|
||||||
elseif opts.scope == 'win' then
|
elseif opts.scope == "win" then
|
||||||
cmd = 'lcd'
|
cmd = "lcd"
|
||||||
end
|
end
|
||||||
cd(cmd, opts.silent)
|
cd(cmd, opts.silent)
|
||||||
end,
|
end,
|
||||||
parameters = {
|
parameters = {
|
||||||
scope = {
|
scope = {
|
||||||
type = 'nil|"tab"|"win"',
|
type = 'nil|"tab"|"win"',
|
||||||
desc = 'Scope of the directory change (e.g. use |:tcd| or |:lcd|)',
|
desc = "Scope of the directory change (e.g. use |:tcd| or |:lcd|)",
|
||||||
},
|
},
|
||||||
silent = {
|
silent = {
|
||||||
type = 'boolean',
|
type = "boolean",
|
||||||
desc = 'Do not show a message when changing directories',
|
desc = "Do not show a message when changing directories",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
M.tcd = {
|
M.tcd = {
|
||||||
desc = ':tcd to the current canola directory',
|
desc = ":tcd to the current oil directory",
|
||||||
deprecated = true,
|
deprecated = true,
|
||||||
callback = function()
|
callback = function()
|
||||||
cd('tcd')
|
cd("tcd")
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
M.open_cwd = {
|
M.open_cwd = {
|
||||||
desc = "Open canola in Neovim's current working directory",
|
desc = "Open oil in Neovim's current working directory",
|
||||||
callback = function()
|
callback = function()
|
||||||
canola.open(vim.fn.getcwd())
|
oil.open(vim.fn.getcwd())
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
M.toggle_hidden = {
|
M.toggle_hidden = {
|
||||||
desc = 'Toggle hidden files and directories',
|
desc = "Toggle hidden files and directories",
|
||||||
callback = function()
|
callback = function()
|
||||||
require('canola.view').toggle_hidden()
|
require("oil.view").toggle_hidden()
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
M.open_terminal = {
|
M.open_terminal = {
|
||||||
desc = 'Open a terminal in the current directory',
|
desc = "Open a terminal in the current directory",
|
||||||
callback = function()
|
callback = function()
|
||||||
local config = require('canola.config')
|
local config = require("oil.config")
|
||||||
local bufname = vim.api.nvim_buf_get_name(0)
|
local bufname = vim.api.nvim_buf_get_name(0)
|
||||||
local adapter = config.get_adapter_by_scheme(bufname)
|
local adapter = config.get_adapter_by_scheme(bufname)
|
||||||
if not adapter then
|
if not adapter then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
if adapter.name == 'files' then
|
if adapter.name == "files" then
|
||||||
local dir = canola.get_current_dir()
|
local dir = oil.get_current_dir()
|
||||||
assert(dir, 'Canola buffer with files adapter must have current directory')
|
assert(dir, "Oil buffer with files adapter must have current directory")
|
||||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||||
vim.api.nvim_set_current_buf(bufnr)
|
vim.api.nvim_set_current_buf(bufnr)
|
||||||
if vim.fn.has('nvim-0.11') == 1 then
|
if vim.fn.has("nvim-0.11") == 1 then
|
||||||
vim.fn.jobstart(vim.o.shell, { cwd = dir, term = true })
|
vim.fn.jobstart(vim.o.shell, { cwd = dir, term = true })
|
||||||
else
|
else
|
||||||
---@diagnostic disable-next-line: deprecated
|
---@diagnostic disable-next-line: deprecated
|
||||||
vim.fn.termopen(vim.o.shell, { cwd = dir })
|
vim.fn.termopen(vim.o.shell, { cwd = dir })
|
||||||
end
|
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('canola.adapters.ssh').parse_url(bufname)
|
local url = require("oil.adapters.ssh").parse_url(bufname)
|
||||||
local cmd = require('canola.adapters.ssh.connection').create_ssh_command(url)
|
local cmd = require("oil.adapters.ssh.connection").create_ssh_command(url)
|
||||||
local term_id
|
local term_id
|
||||||
if vim.fn.has('nvim-0.11') == 1 then
|
if vim.fn.has("nvim-0.11") == 1 then
|
||||||
term_id = vim.fn.jobstart(cmd, { term = true })
|
term_id = vim.fn.jobstart(cmd, { term = true })
|
||||||
else
|
else
|
||||||
---@diagnostic disable-next-line: deprecated
|
---@diagnostic disable-next-line: deprecated
|
||||||
term_id = vim.fn.termopen(cmd)
|
term_id = vim.fn.termopen(cmd)
|
||||||
end
|
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
|
||||||
else
|
else
|
||||||
vim.notify(
|
vim.notify(
|
||||||
|
|
@ -304,28 +304,28 @@ M.open_terminal = {
|
||||||
---@return nil|string[] cmd
|
---@return nil|string[] cmd
|
||||||
---@return nil|string error
|
---@return nil|string error
|
||||||
local function get_open_cmd(path)
|
local function get_open_cmd(path)
|
||||||
if vim.fn.has('mac') == 1 then
|
if vim.fn.has("mac") == 1 then
|
||||||
return { 'open', path }
|
return { "open", path }
|
||||||
elseif vim.fn.has('win32') == 1 then
|
elseif vim.fn.has("win32") == 1 then
|
||||||
if vim.fn.executable('rundll32') == 1 then
|
if vim.fn.executable("rundll32") == 1 then
|
||||||
return { 'rundll32', 'url.dll,FileProtocolHandler', path }
|
return { "rundll32", "url.dll,FileProtocolHandler", path }
|
||||||
else
|
else
|
||||||
return nil, 'rundll32 not found'
|
return nil, "rundll32 not found"
|
||||||
end
|
end
|
||||||
elseif vim.fn.executable('explorer.exe') == 1 then
|
elseif vim.fn.executable("explorer.exe") == 1 then
|
||||||
return { 'explorer.exe', path }
|
return { "explorer.exe", path }
|
||||||
elseif vim.fn.executable('xdg-open') == 1 then
|
elseif vim.fn.executable("xdg-open") == 1 then
|
||||||
return { 'xdg-open', path }
|
return { "xdg-open", path }
|
||||||
else
|
else
|
||||||
return nil, 'no handler found'
|
return nil, "no handler found"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
M.open_external = {
|
M.open_external = {
|
||||||
desc = 'Open the entry under the cursor in an external program',
|
desc = "Open the entry under the cursor in an external program",
|
||||||
callback = function()
|
callback = function()
|
||||||
local entry = canola.get_cursor_entry()
|
local entry = oil.get_cursor_entry()
|
||||||
local dir = canola.get_current_dir()
|
local dir = oil.get_current_dir()
|
||||||
if not entry or not dir then
|
if not entry or not dir then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
@ -338,20 +338,20 @@ M.open_external = {
|
||||||
|
|
||||||
local cmd, err = get_open_cmd(path)
|
local cmd, err = get_open_cmd(path)
|
||||||
if not cmd then
|
if not cmd then
|
||||||
vim.notify(string.format('Could not open %s: %s', path, err), vim.log.levels.ERROR)
|
vim.notify(string.format("Could not open %s: %s", path, err), vim.log.levels.ERROR)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local jid = vim.fn.jobstart(cmd, { detach = true })
|
local jid = vim.fn.jobstart(cmd, { detach = true })
|
||||||
assert(jid > 0, 'Failed to start job')
|
assert(jid > 0, "Failed to start job")
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
M.refresh = {
|
M.refresh = {
|
||||||
desc = 'Refresh current directory list',
|
desc = "Refresh current directory list",
|
||||||
callback = function(opts)
|
callback = function(opts)
|
||||||
opts = opts or {}
|
opts = opts or {}
|
||||||
if vim.bo.modified and not opts.force then
|
if vim.bo.modified and not opts.force then
|
||||||
local ok, choice = pcall(vim.fn.confirm, 'Discard changes?', 'No\nYes')
|
local ok, choice = pcall(vim.fn.confirm, "Discard changes?", "No\nYes")
|
||||||
if not ok or choice ~= 2 then
|
if not ok or choice ~= 2 then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
@ -363,27 +363,27 @@ M.refresh = {
|
||||||
end,
|
end,
|
||||||
parameters = {
|
parameters = {
|
||||||
force = {
|
force = {
|
||||||
desc = 'When true, do not prompt user if they will be discarding changes',
|
desc = "When true, do not prompt user if they will be discarding changes",
|
||||||
type = 'boolean',
|
type = "boolean",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
local function open_cmdline_with_path(path)
|
local function open_cmdline_with_path(path)
|
||||||
local escaped =
|
local escaped =
|
||||||
vim.api.nvim_replace_termcodes(': ' .. vim.fn.fnameescape(path) .. '<Home>', true, false, true)
|
vim.api.nvim_replace_termcodes(": " .. vim.fn.fnameescape(path) .. "<Home>", true, false, true)
|
||||||
vim.api.nvim_feedkeys(escaped, 'n', false)
|
vim.api.nvim_feedkeys(escaped, "n", false)
|
||||||
end
|
end
|
||||||
|
|
||||||
M.open_cmdline = {
|
M.open_cmdline = {
|
||||||
desc = 'Open vim cmdline with current entry as an argument',
|
desc = "Open vim cmdline with current entry as an argument",
|
||||||
callback = function(opts)
|
callback = function(opts)
|
||||||
opts = vim.tbl_deep_extend('keep', opts or {}, {
|
opts = vim.tbl_deep_extend("keep", opts or {}, {
|
||||||
shorten_path = true,
|
shorten_path = true,
|
||||||
})
|
})
|
||||||
local config = require('canola.config')
|
local config = require("oil.config")
|
||||||
local fs = require('canola.fs')
|
local fs = require("oil.fs")
|
||||||
local entry = canola.get_cursor_entry()
|
local entry = oil.get_cursor_entry()
|
||||||
if not entry then
|
if not entry then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
@ -393,7 +393,7 @@ M.open_cmdline = {
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local adapter = config.get_adapter_by_scheme(scheme)
|
local adapter = config.get_adapter_by_scheme(scheme)
|
||||||
if not adapter or not path or adapter.name ~= 'files' then
|
if not adapter or not path or adapter.name ~= "files" then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local fullpath = fs.posix_to_os_path(path) .. entry.name
|
local fullpath = fs.posix_to_os_path(path) .. entry.name
|
||||||
|
|
@ -407,28 +407,28 @@ M.open_cmdline = {
|
||||||
end,
|
end,
|
||||||
parameters = {
|
parameters = {
|
||||||
modify = {
|
modify = {
|
||||||
desc = 'Modify the path with |fnamemodify()| using this as the mods argument',
|
desc = "Modify the path with |fnamemodify()| using this as the mods argument",
|
||||||
type = 'string',
|
type = "string",
|
||||||
},
|
},
|
||||||
shorten_path = {
|
shorten_path = {
|
||||||
desc = 'Use relative paths when possible',
|
desc = "Use relative paths when possible",
|
||||||
type = 'boolean',
|
type = "boolean",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
M.yank_entry = {
|
M.yank_entry = {
|
||||||
desc = 'Yank the filepath of the entry under the cursor to a register',
|
desc = "Yank the filepath of the entry under the cursor to a register",
|
||||||
callback = function(opts)
|
callback = function(opts)
|
||||||
opts = opts or {}
|
opts = opts or {}
|
||||||
local entry = canola.get_cursor_entry()
|
local entry = oil.get_cursor_entry()
|
||||||
local dir = canola.get_current_dir()
|
local dir = oil.get_current_dir()
|
||||||
if not entry or not dir then
|
if not entry or not dir then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local name = entry.name
|
local name = entry.name
|
||||||
if entry.type == 'directory' then
|
if entry.type == "directory" then
|
||||||
name = name .. '/'
|
name = name .. "/"
|
||||||
end
|
end
|
||||||
local path = dir .. name
|
local path = dir .. name
|
||||||
if opts.modify then
|
if opts.modify then
|
||||||
|
|
@ -438,18 +438,18 @@ M.yank_entry = {
|
||||||
end,
|
end,
|
||||||
parameters = {
|
parameters = {
|
||||||
modify = {
|
modify = {
|
||||||
desc = 'Modify the path with |fnamemodify()| using this as the mods argument',
|
desc = "Modify the path with |fnamemodify()| using this as the mods argument",
|
||||||
type = 'string',
|
type = "string",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
M.copy_entry_path = {
|
M.copy_entry_path = {
|
||||||
desc = 'Yank the filepath of the entry under the cursor to a register',
|
desc = "Yank the filepath of the entry under the cursor to a register",
|
||||||
deprecated = true,
|
deprecated = true,
|
||||||
callback = function()
|
callback = function()
|
||||||
local entry = canola.get_cursor_entry()
|
local entry = oil.get_cursor_entry()
|
||||||
local dir = canola.get_current_dir()
|
local dir = oil.get_current_dir()
|
||||||
if not entry or not dir then
|
if not entry or not dir then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
@ -458,10 +458,10 @@ M.copy_entry_path = {
|
||||||
}
|
}
|
||||||
|
|
||||||
M.copy_entry_filename = {
|
M.copy_entry_filename = {
|
||||||
desc = 'Yank the filename of the entry under the cursor to a register',
|
desc = "Yank the filename of the entry under the cursor to a register",
|
||||||
deprecated = true,
|
deprecated = true,
|
||||||
callback = function()
|
callback = function()
|
||||||
local entry = canola.get_cursor_entry()
|
local entry = oil.get_cursor_entry()
|
||||||
if not entry then
|
if not entry then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
@ -470,31 +470,31 @@ M.copy_entry_filename = {
|
||||||
}
|
}
|
||||||
|
|
||||||
M.copy_to_system_clipboard = {
|
M.copy_to_system_clipboard = {
|
||||||
desc = 'Copy the entry under the cursor to the system clipboard',
|
desc = "Copy the entry under the cursor to the system clipboard",
|
||||||
callback = function()
|
callback = function()
|
||||||
require('canola.clipboard').copy_to_system_clipboard()
|
require("oil.clipboard").copy_to_system_clipboard()
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
M.paste_from_system_clipboard = {
|
M.paste_from_system_clipboard = {
|
||||||
desc = 'Paste the system clipboard into the current canola directory',
|
desc = "Paste the system clipboard into the current oil directory",
|
||||||
callback = function(opts)
|
callback = function(opts)
|
||||||
require('canola.clipboard').paste_from_system_clipboard(opts and opts.delete_original)
|
require("oil.clipboard").paste_from_system_clipboard(opts and opts.delete_original)
|
||||||
end,
|
end,
|
||||||
parameters = {
|
parameters = {
|
||||||
delete_original = {
|
delete_original = {
|
||||||
type = 'boolean',
|
type = "boolean",
|
||||||
desc = 'Delete the original file after copying',
|
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,
|
||||||
callback = function()
|
callback = function()
|
||||||
local fs = require('canola.fs')
|
local fs = require("oil.fs")
|
||||||
local dir = canola.get_current_dir()
|
local dir = oil.get_current_dir()
|
||||||
if dir then
|
if dir then
|
||||||
open_cmdline_with_path(fs.shorten_path(dir))
|
open_cmdline_with_path(fs.shorten_path(dir))
|
||||||
end
|
end
|
||||||
|
|
@ -502,30 +502,30 @@ M.open_cmdline_dir = {
|
||||||
}
|
}
|
||||||
|
|
||||||
M.change_sort = {
|
M.change_sort = {
|
||||||
desc = 'Change the sort order',
|
desc = "Change the sort order",
|
||||||
callback = function(opts)
|
callback = function(opts)
|
||||||
opts = opts or {}
|
opts = opts or {}
|
||||||
|
|
||||||
if opts.sort then
|
if opts.sort then
|
||||||
canola.set_sort(opts.sort)
|
oil.set_sort(opts.sort)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local sort_cols = { 'name', 'size', 'atime', 'mtime', 'ctime', 'birthtime' }
|
local sort_cols = { "name", "size", "atime", "mtime", "ctime", "birthtime" }
|
||||||
vim.ui.select(sort_cols, { prompt = 'Sort by', kind = 'canola_sort_col' }, function(col)
|
vim.ui.select(sort_cols, { prompt = "Sort by", kind = "oil_sort_col" }, function(col)
|
||||||
if not col then
|
if not col then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
vim.ui.select(
|
vim.ui.select(
|
||||||
{ 'ascending', 'descending' },
|
{ "ascending", "descending" },
|
||||||
{ prompt = 'Sort order', kind = 'canola_sort_order' },
|
{ prompt = "Sort order", kind = "oil_sort_order" },
|
||||||
function(order)
|
function(order)
|
||||||
if not order then
|
if not order then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
order = order == 'ascending' and 'asc' or 'desc'
|
order = order == "ascending" and "asc" or "desc"
|
||||||
canola.set_sort({
|
oil.set_sort({
|
||||||
{ 'type', 'asc' },
|
{ "type", "asc" },
|
||||||
{ col, order },
|
{ col, order },
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
@ -534,47 +534,47 @@ M.change_sort = {
|
||||||
end,
|
end,
|
||||||
parameters = {
|
parameters = {
|
||||||
sort = {
|
sort = {
|
||||||
type = 'canola.SortSpec[]',
|
type = "oil.SortSpec[]",
|
||||||
desc = 'List of columns plus direction (see |canola.set_sort|) instead of interactive selection',
|
desc = "List of columns plus direction (see |oil.set_sort|) instead of interactive selection",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
M.toggle_trash = {
|
M.toggle_trash = {
|
||||||
desc = 'Jump to and from the trash for the current directory',
|
desc = "Jump to and from the trash for the current directory",
|
||||||
callback = function()
|
callback = function()
|
||||||
local fs = require('canola.fs')
|
local fs = require("oil.fs")
|
||||||
local bufname = vim.api.nvim_buf_get_name(0)
|
local bufname = vim.api.nvim_buf_get_name(0)
|
||||||
local scheme, path = util.parse_url(bufname)
|
local scheme, path = util.parse_url(bufname)
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
local bufnr = vim.api.nvim_get_current_buf()
|
||||||
local url
|
local url
|
||||||
if scheme == 'canola://' then
|
if scheme == "oil://" then
|
||||||
url = 'canola-trash://' .. path
|
url = "oil-trash://" .. path
|
||||||
elseif scheme == 'canola-trash://' then
|
elseif scheme == "oil-trash://" then
|
||||||
url = 'canola://' .. path
|
url = "oil://" .. path
|
||||||
-- The non-linux trash implementations don't support per-directory trash,
|
-- The non-linux trash implementations don't support per-directory trash,
|
||||||
-- so jump back to the stored source buffer.
|
-- so jump back to the stored source buffer.
|
||||||
if not fs.is_linux then
|
if not fs.is_linux then
|
||||||
local src_bufnr = vim.b.canola_trash_toggle_src
|
local src_bufnr = vim.b.oil_trash_toggle_src
|
||||||
if src_bufnr and vim.api.nvim_buf_is_valid(src_bufnr) then
|
if src_bufnr and vim.api.nvim_buf_is_valid(src_bufnr) then
|
||||||
url = vim.api.nvim_buf_get_name(src_bufnr)
|
url = vim.api.nvim_buf_get_name(src_bufnr)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
vim.notify('No trash found for buffer', vim.log.levels.WARN)
|
vim.notify("No trash found for buffer", vim.log.levels.WARN)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
vim.cmd.edit({ args = { url } })
|
vim.cmd.edit({ args = { url } })
|
||||||
vim.b.canola_trash_toggle_src = bufnr
|
vim.b.oil_trash_toggle_src = bufnr
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
M.send_to_qflist = {
|
M.send_to_qflist = {
|
||||||
desc = 'Sends files in the current canola directory to the quickfix list, replacing the previous entries.',
|
desc = "Sends files in the current oil directory to the quickfix list, replacing the previous entries.",
|
||||||
callback = function(opts)
|
callback = function(opts)
|
||||||
opts = vim.tbl_deep_extend('keep', opts or {}, {
|
opts = vim.tbl_deep_extend("keep", opts or {}, {
|
||||||
target = 'qflist',
|
target = "qflist",
|
||||||
action = 'r',
|
action = "r",
|
||||||
only_matching_search = false,
|
only_matching_search = false,
|
||||||
})
|
})
|
||||||
util.send_to_quickfix({
|
util.send_to_quickfix({
|
||||||
|
|
@ -586,48 +586,48 @@ M.send_to_qflist = {
|
||||||
parameters = {
|
parameters = {
|
||||||
target = {
|
target = {
|
||||||
type = '"qflist"|"loclist"',
|
type = '"qflist"|"loclist"',
|
||||||
desc = 'The target list to send files to',
|
desc = "The target list to send files to",
|
||||||
},
|
},
|
||||||
action = {
|
action = {
|
||||||
type = '"r"|"a"',
|
type = '"r"|"a"',
|
||||||
desc = 'Replace or add to current quickfix list (see |setqflist-action|)',
|
desc = "Replace or add to current quickfix list (see |setqflist-action|)",
|
||||||
},
|
},
|
||||||
only_matching_search = {
|
only_matching_search = {
|
||||||
type = 'boolean',
|
type = "boolean",
|
||||||
desc = 'Whether to only add the files that matches the last search. This option only applies when search highlighting is active',
|
desc = "Whether to only add the files that matches the last search. This option only applies when search highlighting is active",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
M.add_to_qflist = {
|
M.add_to_qflist = {
|
||||||
desc = 'Adds files in the current canola directory to the quickfix list, keeping the previous entries.',
|
desc = "Adds files in the current oil directory to the quickfix list, keeping the previous entries.",
|
||||||
deprecated = true,
|
deprecated = true,
|
||||||
callback = function()
|
callback = function()
|
||||||
util.send_to_quickfix({
|
util.send_to_quickfix({
|
||||||
target = 'qflist',
|
target = "qflist",
|
||||||
mode = 'a',
|
mode = "a",
|
||||||
})
|
})
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
M.send_to_loclist = {
|
M.send_to_loclist = {
|
||||||
desc = 'Sends files in the current canola directory to the location list, replacing the previous entries.',
|
desc = "Sends files in the current oil directory to the location list, replacing the previous entries.",
|
||||||
deprecated = true,
|
deprecated = true,
|
||||||
callback = function()
|
callback = function()
|
||||||
util.send_to_quickfix({
|
util.send_to_quickfix({
|
||||||
target = 'loclist',
|
target = "loclist",
|
||||||
mode = 'r',
|
mode = "r",
|
||||||
})
|
})
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
M.add_to_loclist = {
|
M.add_to_loclist = {
|
||||||
desc = 'Adds files in the current canola directory to the location list, keeping the previous entries.',
|
desc = "Adds files in the current oil directory to the location list, keeping the previous entries.",
|
||||||
deprecated = true,
|
deprecated = true,
|
||||||
callback = function()
|
callback = function()
|
||||||
util.send_to_quickfix({
|
util.send_to_quickfix({
|
||||||
target = 'loclist',
|
target = "loclist",
|
||||||
mode = 'a',
|
mode = "a",
|
||||||
})
|
})
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
@ -637,7 +637,7 @@ M.add_to_loclist = {
|
||||||
M._get_actions = function()
|
M._get_actions = function()
|
||||||
local ret = {}
|
local ret = {}
|
||||||
for name, action in pairs(M) do
|
for name, action in pairs(M) do
|
||||||
if type(action) == 'table' and action.desc then
|
if type(action) == "table" and action.desc then
|
||||||
table.insert(ret, {
|
table.insert(ret, {
|
||||||
name = name,
|
name = name,
|
||||||
desc = action.desc,
|
desc = action.desc,
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
local cache = require('canola.cache')
|
local cache = require("oil.cache")
|
||||||
local columns = require('canola.columns')
|
local columns = require("oil.columns")
|
||||||
local config = require('canola.config')
|
local config = require("oil.config")
|
||||||
local constants = require('canola.constants')
|
local constants = require("oil.constants")
|
||||||
local fs = require('canola.fs')
|
local fs = require("oil.fs")
|
||||||
local git = require('canola.git')
|
local git = require("oil.git")
|
||||||
local log = require('canola.log')
|
local log = require("oil.log")
|
||||||
local permissions = require('canola.adapters.files.permissions')
|
local permissions = require("oil.adapters.files.permissions")
|
||||||
local util = require('canola.util')
|
local util = require("oil.util")
|
||||||
local uv = vim.uv or vim.loop
|
local uv = vim.uv or vim.loop
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
@ -25,7 +25,7 @@ local function read_link_data(path, cb)
|
||||||
assert(link)
|
assert(link)
|
||||||
local stat_path = link
|
local stat_path = link
|
||||||
if not fs.is_absolute(link) then
|
if not fs.is_absolute(link) then
|
||||||
stat_path = fs.join(vim.fn.fnamemodify(path, ':h'), link)
|
stat_path = fs.join(vim.fn.fnamemodify(path, ":h"), link)
|
||||||
end
|
end
|
||||||
uv.fs_stat(stat_path, function(stat_err, stat)
|
uv.fs_stat(stat_path, function(stat_err, stat)
|
||||||
cb(nil, link, stat)
|
cb(nil, link, stat)
|
||||||
|
|
@ -35,15 +35,15 @@ local function read_link_data(path, cb)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class (exact) canola.FilesAdapter: canola.Adapter
|
---@class (exact) oil.FilesAdapter: oil.Adapter
|
||||||
---@field to_short_os_path fun(path: string, entry_type: nil|canola.EntryType): string
|
---@field to_short_os_path fun(path: string, entry_type: nil|oil.EntryType): string
|
||||||
|
|
||||||
---@param path string
|
---@param path string
|
||||||
---@param entry_type nil|canola.EntryType
|
---@param entry_type nil|oil.EntryType
|
||||||
---@return string
|
---@return string
|
||||||
M.to_short_os_path = function(path, entry_type)
|
M.to_short_os_path = function(path, entry_type)
|
||||||
local shortpath = fs.shorten_path(fs.posix_to_os_path(path))
|
local shortpath = fs.shorten_path(fs.posix_to_os_path(path))
|
||||||
if entry_type == 'directory' then
|
if entry_type == "directory" then
|
||||||
shortpath = util.addslash(shortpath, true)
|
shortpath = util.addslash(shortpath, true)
|
||||||
end
|
end
|
||||||
return shortpath
|
return shortpath
|
||||||
|
|
@ -60,17 +60,14 @@ file_columns.size = {
|
||||||
if not stat then
|
if not stat then
|
||||||
return columns.EMPTY
|
return columns.EMPTY
|
||||||
end
|
end
|
||||||
if entry[FIELD_TYPE] == 'directory' then
|
|
||||||
return columns.EMPTY
|
|
||||||
end
|
|
||||||
if stat.size >= 1e9 then
|
if stat.size >= 1e9 then
|
||||||
return string.format('%.1fG', stat.size / 1e9)
|
return string.format("%.1fG", stat.size / 1e9)
|
||||||
elseif stat.size >= 1e6 then
|
elseif stat.size >= 1e6 then
|
||||||
return string.format('%.1fM', stat.size / 1e6)
|
return string.format("%.1fM", stat.size / 1e6)
|
||||||
elseif stat.size >= 1e3 then
|
elseif stat.size >= 1e3 then
|
||||||
return string.format('%.1fk', stat.size / 1e3)
|
return string.format("%.1fk", stat.size / 1e3)
|
||||||
else
|
else
|
||||||
return string.format('%d', stat.size)
|
return string.format("%d", stat.size)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
|
@ -85,7 +82,7 @@ file_columns.size = {
|
||||||
end,
|
end,
|
||||||
|
|
||||||
parse = function(line, conf)
|
parse = function(line, conf)
|
||||||
return line:match('^(%d+%S*)%s+(.*)$')
|
return line:match("^(%d+%S*)%s+(.*)$")
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -123,7 +120,7 @@ if not fs.is_windows then
|
||||||
local _, path = util.parse_url(action.url)
|
local _, path = util.parse_url(action.url)
|
||||||
assert(path)
|
assert(path)
|
||||||
return string.format(
|
return string.format(
|
||||||
'CHMOD %s %s',
|
"CHMOD %s %s",
|
||||||
permissions.mode_to_octal_str(action.value),
|
permissions.mode_to_octal_str(action.value),
|
||||||
M.to_short_os_path(path, action.entry_type)
|
M.to_short_os_path(path, action.entry_type)
|
||||||
)
|
)
|
||||||
|
|
@ -150,10 +147,10 @@ end
|
||||||
local current_year
|
local current_year
|
||||||
-- Make sure we run this import-time effect in the main loop (mostly for tests)
|
-- Make sure we run this import-time effect in the main loop (mostly for tests)
|
||||||
vim.schedule(function()
|
vim.schedule(function()
|
||||||
current_year = vim.fn.strftime('%Y')
|
current_year = vim.fn.strftime("%Y")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
for _, time_key in ipairs({ 'ctime', 'mtime', 'atime', 'birthtime' }) do
|
for _, time_key in ipairs({ "ctime", "mtime", "atime", "birthtime" }) do
|
||||||
file_columns[time_key] = {
|
file_columns[time_key] = {
|
||||||
require_stat = true,
|
require_stat = true,
|
||||||
|
|
||||||
|
|
@ -168,11 +165,11 @@ for _, time_key in ipairs({ 'ctime', 'mtime', 'atime', 'birthtime' }) do
|
||||||
if fmt then
|
if fmt then
|
||||||
ret = vim.fn.strftime(fmt, stat[time_key].sec)
|
ret = vim.fn.strftime(fmt, stat[time_key].sec)
|
||||||
else
|
else
|
||||||
local year = vim.fn.strftime('%Y', stat[time_key].sec)
|
local year = vim.fn.strftime("%Y", stat[time_key].sec)
|
||||||
if year ~= current_year then
|
if year ~= current_year then
|
||||||
ret = vim.fn.strftime('%b %d %Y', stat[time_key].sec)
|
ret = vim.fn.strftime("%b %d %Y", stat[time_key].sec)
|
||||||
else
|
else
|
||||||
ret = vim.fn.strftime('%b %d %H:%M', stat[time_key].sec)
|
ret = vim.fn.strftime("%b %d %H:%M", stat[time_key].sec)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return ret
|
return ret
|
||||||
|
|
@ -186,20 +183,20 @@ for _, time_key in ipairs({ 'ctime', 'mtime', 'atime', 'birthtime' }) do
|
||||||
-- 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
|
pattern = fmt
|
||||||
:gsub('%%.', '%%S+')
|
:gsub("%%.", "%%S+")
|
||||||
:gsub('%s+', '%%s+')
|
:gsub("%s+", "%%s+")
|
||||||
-- escape `()[]` because those are special characters in Lua patterns
|
-- escape `()[]` because those are special characters in Lua patterns
|
||||||
:gsub(
|
:gsub(
|
||||||
'%(',
|
"%(",
|
||||||
'%%('
|
"%%("
|
||||||
)
|
)
|
||||||
:gsub('%)', '%%)')
|
:gsub("%)", "%%)")
|
||||||
: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
|
||||||
return line:match('^(' .. pattern .. ')%s+(.+)$')
|
return line:match("^(" .. pattern .. ")%s+(.+)$")
|
||||||
end,
|
end,
|
||||||
|
|
||||||
get_sort_value = function(entry)
|
get_sort_value = function(entry)
|
||||||
|
|
@ -229,7 +226,7 @@ local function columns_require_stat(column_defs)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param name string
|
---@param name string
|
||||||
---@return nil|canola.ColumnDefinition
|
---@return nil|oil.ColumnDefinition
|
||||||
M.get_column = function(name)
|
M.get_column = function(name)
|
||||||
return file_columns[name]
|
return file_columns[name]
|
||||||
end
|
end
|
||||||
|
|
@ -241,17 +238,17 @@ M.normalize_url = function(url, callback)
|
||||||
assert(path)
|
assert(path)
|
||||||
|
|
||||||
if fs.is_windows then
|
if fs.is_windows then
|
||||||
if path == '/' then
|
if path == "/" then
|
||||||
return callback(url)
|
return callback(url)
|
||||||
else
|
else
|
||||||
local is_root_drive = path:match('^/%u$')
|
local is_root_drive = path:match("^/%u$")
|
||||||
if is_root_drive then
|
if is_root_drive then
|
||||||
return callback(url .. '/')
|
return callback(url .. "/")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local os_path = vim.fn.fnamemodify(fs.posix_to_os_path(path), ':p')
|
local os_path = vim.fn.fnamemodify(fs.posix_to_os_path(path), ":p")
|
||||||
uv.fs_realpath(os_path, function(err, new_os_path)
|
uv.fs_realpath(os_path, function(err, new_os_path)
|
||||||
local realpath
|
local realpath
|
||||||
if fs.is_windows then
|
if fs.is_windows then
|
||||||
|
|
@ -267,8 +264,8 @@ M.normalize_url = function(url, callback)
|
||||||
vim.schedule_wrap(function(stat_err, stat)
|
vim.schedule_wrap(function(stat_err, stat)
|
||||||
local is_directory
|
local is_directory
|
||||||
if stat then
|
if stat then
|
||||||
is_directory = stat.type == 'directory'
|
is_directory = stat.type == "directory"
|
||||||
elseif vim.endswith(realpath, '/') or (fs.is_windows and vim.endswith(realpath, '\\')) then
|
elseif vim.endswith(realpath, "/") or (fs.is_windows and vim.endswith(realpath, "\\")) then
|
||||||
is_directory = true
|
is_directory = true
|
||||||
else
|
else
|
||||||
local filetype = vim.filetype.match({ filename = vim.fs.basename(realpath) })
|
local filetype = vim.filetype.match({ filename = vim.fs.basename(realpath) })
|
||||||
|
|
@ -279,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(vim.fn.fnamemodify(realpath, ':.'))
|
callback(vim.fn.fnamemodify(realpath, ":."))
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
)
|
)
|
||||||
|
|
@ -287,7 +284,7 @@ M.normalize_url = function(url, callback)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param url string
|
---@param url string
|
||||||
---@param entry canola.Entry
|
---@param entry oil.Entry
|
||||||
---@param cb fun(path: nil|string)
|
---@param cb fun(path: nil|string)
|
||||||
M.get_entry_path = function(url, entry, cb)
|
M.get_entry_path = function(url, entry, cb)
|
||||||
if entry.id then
|
if entry.id then
|
||||||
|
|
@ -295,18 +292,18 @@ M.get_entry_path = function(url, entry, cb)
|
||||||
local scheme, path = util.parse_url(parent_url)
|
local scheme, path = util.parse_url(parent_url)
|
||||||
M.normalize_url(scheme .. path .. entry.name, cb)
|
M.normalize_url(scheme .. path .. entry.name, cb)
|
||||||
else
|
else
|
||||||
if entry.type == 'directory' then
|
if entry.type == "directory" then
|
||||||
cb(url)
|
cb(url)
|
||||||
else
|
else
|
||||||
local _, path = util.parse_url(url)
|
local _, path = util.parse_url(url)
|
||||||
local os_path = vim.fn.fnamemodify(fs.posix_to_os_path(assert(path)), ':p')
|
local os_path = vim.fn.fnamemodify(fs.posix_to_os_path(assert(path)), ":p")
|
||||||
cb(os_path)
|
cb(os_path)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param parent_dir string
|
---@param parent_dir string
|
||||||
---@param entry canola.InternalEntry
|
---@param entry oil.InternalEntry
|
||||||
---@param require_stat boolean
|
---@param require_stat boolean
|
||||||
---@param cb fun(err?: string)
|
---@param cb fun(err?: string)
|
||||||
local function fetch_entry_metadata(parent_dir, entry, require_stat, cb)
|
local function fetch_entry_metadata(parent_dir, entry, require_stat, cb)
|
||||||
|
|
@ -318,16 +315,16 @@ local function fetch_entry_metadata(parent_dir, entry, require_stat, cb)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Sometimes fs_readdir entries don't have a type, so we need to stat them.
|
-- Sometimes fs_readdir entries don't have a type, so we need to stat them.
|
||||||
-- See https://github.com/stevearc/canola.nvim/issues/543
|
-- See https://github.com/stevearc/oil.nvim/issues/543
|
||||||
if not require_stat and not entry[FIELD_TYPE] then
|
if not require_stat and not entry[FIELD_TYPE] then
|
||||||
require_stat = true
|
require_stat = true
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Make sure we always get fs_stat info for links
|
-- Make sure we always get fs_stat info for links
|
||||||
if entry[FIELD_TYPE] == 'link' then
|
if entry[FIELD_TYPE] == "link" then
|
||||||
read_link_data(entry_path, function(link_err, link, link_stat)
|
read_link_data(entry_path, function(link_err, link, link_stat)
|
||||||
if link_err then
|
if link_err then
|
||||||
log.warn('Error reading link data %s: %s', entry_path, link_err)
|
log.warn("Error reading link data %s: %s", entry_path, link_err)
|
||||||
return cb()
|
return cb()
|
||||||
end
|
end
|
||||||
meta.link = link
|
meta.link = link
|
||||||
|
|
@ -339,7 +336,7 @@ local function fetch_entry_metadata(parent_dir, entry, require_stat, cb)
|
||||||
-- The link is broken, so let's use the stat of the link itself
|
-- The link is broken, so let's use the stat of the link itself
|
||||||
uv.fs_lstat(entry_path, function(stat_err, stat)
|
uv.fs_lstat(entry_path, function(stat_err, stat)
|
||||||
if stat_err then
|
if stat_err then
|
||||||
log.warn('Error lstat link file %s: %s', entry_path, stat_err)
|
log.warn("Error lstat link file %s: %s", entry_path, stat_err)
|
||||||
return cb()
|
return cb()
|
||||||
end
|
end
|
||||||
meta.stat = stat
|
meta.stat = stat
|
||||||
|
|
@ -353,7 +350,7 @@ local function fetch_entry_metadata(parent_dir, entry, require_stat, cb)
|
||||||
elseif require_stat then
|
elseif require_stat then
|
||||||
uv.fs_stat(entry_path, function(stat_err, stat)
|
uv.fs_stat(entry_path, function(stat_err, stat)
|
||||||
if stat_err then
|
if stat_err then
|
||||||
log.warn('Error stat file %s: %s', entry_path, stat_err)
|
log.warn("Error stat file %s: %s", entry_path, stat_err)
|
||||||
return cb()
|
return cb()
|
||||||
end
|
end
|
||||||
assert(stat)
|
assert(stat)
|
||||||
|
|
@ -367,15 +364,15 @@ local function fetch_entry_metadata(parent_dir, entry, require_stat, cb)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- On windows, sometimes the entry type from fs_readdir is "link" but the actual type is not.
|
-- On windows, sometimes the entry type from fs_readdir is "link" but the actual type is not.
|
||||||
-- See https://github.com/stevearc/canola.nvim/issues/535
|
-- See https://github.com/stevearc/oil.nvim/issues/535
|
||||||
if fs.is_windows then
|
if fs.is_windows then
|
||||||
local old_fetch_metadata = fetch_entry_metadata
|
local old_fetch_metadata = fetch_entry_metadata
|
||||||
fetch_entry_metadata = function(parent_dir, entry, require_stat, cb)
|
fetch_entry_metadata = function(parent_dir, entry, require_stat, cb)
|
||||||
if entry[FIELD_TYPE] == 'link' then
|
if entry[FIELD_TYPE] == "link" then
|
||||||
local entry_path = fs.posix_to_os_path(parent_dir .. entry[FIELD_NAME])
|
local entry_path = fs.posix_to_os_path(parent_dir .. entry[FIELD_NAME])
|
||||||
uv.fs_lstat(entry_path, function(stat_err, stat)
|
uv.fs_lstat(entry_path, function(stat_err, stat)
|
||||||
if stat_err then
|
if stat_err then
|
||||||
log.warn('Error lstat link file %s: %s', entry_path, stat_err)
|
log.warn("Error lstat link file %s: %s", entry_path, stat_err)
|
||||||
return old_fetch_metadata(parent_dir, entry, require_stat, cb)
|
return old_fetch_metadata(parent_dir, entry, require_stat, cb)
|
||||||
end
|
end
|
||||||
assert(stat)
|
assert(stat)
|
||||||
|
|
@ -396,22 +393,22 @@ end
|
||||||
|
|
||||||
---@param url string
|
---@param url string
|
||||||
---@param column_defs string[]
|
---@param column_defs string[]
|
||||||
---@param cb fun(err?: string, entries?: canola.InternalEntry[], fetch_more?: fun())
|
---@param cb fun(err?: string, entries?: oil.InternalEntry[], fetch_more?: fun())
|
||||||
local function list_windows_drives(url, column_defs, cb)
|
local function list_windows_drives(url, column_defs, cb)
|
||||||
local _, path = util.parse_url(url)
|
local _, path = util.parse_url(url)
|
||||||
assert(path)
|
assert(path)
|
||||||
local require_stat = columns_require_stat(column_defs)
|
local require_stat = columns_require_stat(column_defs)
|
||||||
local stdout = ''
|
local stdout = ""
|
||||||
local jid = vim.fn.jobstart({ 'wmic', 'logicaldisk', 'get', 'name' }, {
|
local jid = vim.fn.jobstart({ "wmic", "logicaldisk", "get", "name" }, {
|
||||||
stdout_buffered = true,
|
stdout_buffered = true,
|
||||||
on_stdout = function(_, data)
|
on_stdout = function(_, data)
|
||||||
stdout = table.concat(data, '\n')
|
stdout = table.concat(data, "\n")
|
||||||
end,
|
end,
|
||||||
on_exit = function(_, code)
|
on_exit = function(_, code)
|
||||||
if code ~= 0 then
|
if code ~= 0 then
|
||||||
return cb('Error listing windows devices')
|
return cb("Error listing windows devices")
|
||||||
end
|
end
|
||||||
local lines = vim.split(stdout, '\n', { plain = true, trimempty = true })
|
local lines = vim.split(stdout, "\n", { plain = true, trimempty = true })
|
||||||
-- Remove the "Name" header
|
-- Remove the "Name" header
|
||||||
table.remove(lines, 1)
|
table.remove(lines, 1)
|
||||||
local internal_entries = {}
|
local internal_entries = {}
|
||||||
|
|
@ -424,12 +421,12 @@ local function list_windows_drives(url, column_defs, cb)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
for _, disk in ipairs(lines) do
|
for _, disk in ipairs(lines) do
|
||||||
if disk:match('^%s*$') then
|
if disk:match("^%s*$") then
|
||||||
-- Skip empty line
|
-- Skip empty line
|
||||||
complete_disk_cb()
|
complete_disk_cb()
|
||||||
else
|
else
|
||||||
disk = disk:gsub(':%s*$', '')
|
disk = disk:gsub(":%s*$", "")
|
||||||
local cache_entry = cache.create_entry(url, disk, 'directory')
|
local cache_entry = cache.create_entry(url, disk, "directory")
|
||||||
table.insert(internal_entries, cache_entry)
|
table.insert(internal_entries, cache_entry)
|
||||||
fetch_entry_metadata(path, cache_entry, require_stat, complete_disk_cb)
|
fetch_entry_metadata(path, cache_entry, require_stat, complete_disk_cb)
|
||||||
end
|
end
|
||||||
|
|
@ -437,17 +434,17 @@ local function list_windows_drives(url, column_defs, cb)
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
if jid <= 0 then
|
if jid <= 0 then
|
||||||
cb('Could not list windows devices')
|
cb("Could not list windows devices")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param url string
|
---@param url string
|
||||||
---@param column_defs string[]
|
---@param column_defs string[]
|
||||||
---@param cb fun(err?: string, entries?: canola.InternalEntry[], fetch_more?: fun())
|
---@param cb fun(err?: string, entries?: oil.InternalEntry[], fetch_more?: fun())
|
||||||
M.list = function(url, column_defs, cb)
|
M.list = function(url, column_defs, cb)
|
||||||
local _, path = util.parse_url(url)
|
local _, path = util.parse_url(url)
|
||||||
assert(path)
|
assert(path)
|
||||||
if fs.is_windows and path == '/' then
|
if fs.is_windows and path == "/" then
|
||||||
return list_windows_drives(url, column_defs, cb)
|
return list_windows_drives(url, column_defs, cb)
|
||||||
end
|
end
|
||||||
local dir = fs.posix_to_os_path(path)
|
local dir = fs.posix_to_os_path(path)
|
||||||
|
|
@ -456,7 +453,7 @@ M.list = function(url, column_defs, cb)
|
||||||
---@diagnostic disable-next-line: param-type-mismatch, discard-returns
|
---@diagnostic disable-next-line: param-type-mismatch, discard-returns
|
||||||
uv.fs_opendir(dir, function(open_err, fd)
|
uv.fs_opendir(dir, function(open_err, fd)
|
||||||
if open_err then
|
if open_err then
|
||||||
if open_err:match('^ENOENT: no such file or directory') then
|
if open_err:match("^ENOENT: no such file or directory") then
|
||||||
-- If the directory doesn't exist, treat the list as a success. We will be able to traverse
|
-- If the directory doesn't exist, treat the list as a success. We will be able to traverse
|
||||||
-- and edit a not-yet-existing directory.
|
-- and edit a not-yet-existing directory.
|
||||||
return cb()
|
return cb()
|
||||||
|
|
@ -508,7 +505,7 @@ M.is_modifiable = function(bufnr)
|
||||||
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
||||||
local _, path = util.parse_url(bufname)
|
local _, path = util.parse_url(bufname)
|
||||||
assert(path)
|
assert(path)
|
||||||
if fs.is_windows and path == '/' then
|
if fs.is_windows and path == "/" then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
local dir = fs.posix_to_os_path(path)
|
local dir = fs.posix_to_os_path(path)
|
||||||
|
|
@ -518,30 +515,30 @@ M.is_modifiable = function(bufnr)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- fs_access can return nil, force boolean return
|
-- fs_access can return nil, force boolean return
|
||||||
return uv.fs_access(dir, 'W') == true
|
return uv.fs_access(dir, "W") == true
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param action canola.Action
|
---@param action oil.Action
|
||||||
---@return string
|
---@return string
|
||||||
M.render_action = function(action)
|
M.render_action = function(action)
|
||||||
if action.type == 'create' then
|
if action.type == "create" then
|
||||||
local _, path = util.parse_url(action.url)
|
local _, path = util.parse_url(action.url)
|
||||||
assert(path)
|
assert(path)
|
||||||
local ret = string.format('CREATE %s', M.to_short_os_path(path, action.entry_type))
|
local ret = string.format("CREATE %s", M.to_short_os_path(path, action.entry_type))
|
||||||
if action.link then
|
if action.link then
|
||||||
ret = ret .. ' -> ' .. fs.posix_to_os_path(action.link)
|
ret = ret .. " -> " .. fs.posix_to_os_path(action.link)
|
||||||
end
|
end
|
||||||
return ret
|
return ret
|
||||||
elseif action.type == 'delete' then
|
elseif action.type == "delete" then
|
||||||
local _, path = util.parse_url(action.url)
|
local _, path = util.parse_url(action.url)
|
||||||
assert(path)
|
assert(path)
|
||||||
local short_path = M.to_short_os_path(path, action.entry_type)
|
local short_path = M.to_short_os_path(path, action.entry_type)
|
||||||
if config.delete_to_trash then
|
if config.delete_to_trash then
|
||||||
return string.format(' TRASH %s', short_path)
|
return string.format(" TRASH %s", short_path)
|
||||||
else
|
else
|
||||||
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 = assert(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)
|
||||||
|
|
@ -549,7 +546,7 @@ M.render_action = function(action)
|
||||||
local _, dest_path = util.parse_url(action.dest_url)
|
local _, dest_path = util.parse_url(action.dest_url)
|
||||||
assert(dest_path)
|
assert(dest_path)
|
||||||
return string.format(
|
return string.format(
|
||||||
' %s %s -> %s',
|
" %s %s -> %s",
|
||||||
action.type:upper(),
|
action.type:upper(),
|
||||||
M.to_short_os_path(src_path, action.entry_type),
|
M.to_short_os_path(src_path, action.entry_type),
|
||||||
M.to_short_os_path(dest_path, action.entry_type)
|
M.to_short_os_path(dest_path, action.entry_type)
|
||||||
|
|
@ -563,10 +560,10 @@ M.render_action = function(action)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param action canola.Action
|
---@param action oil.Action
|
||||||
---@param cb fun(err: nil|string)
|
---@param cb fun(err: nil|string)
|
||||||
M.perform_action = function(action, cb)
|
M.perform_action = function(action, cb)
|
||||||
if action.type == 'create' then
|
if action.type == "create" then
|
||||||
local _, path = util.parse_url(action.url)
|
local _, path = util.parse_url(action.url)
|
||||||
assert(path)
|
assert(path)
|
||||||
path = fs.posix_to_os_path(path)
|
path = fs.posix_to_os_path(path)
|
||||||
|
|
@ -582,16 +579,16 @@ M.perform_action = function(action, cb)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
if action.entry_type == 'directory' then
|
if action.entry_type == "directory" then
|
||||||
uv.fs_mkdir(path, config.new_dir_mode, function(err)
|
uv.fs_mkdir(path, config.new_dir_mode, function(err)
|
||||||
-- Ignore if the directory already exists
|
-- Ignore if the directory already exists
|
||||||
if not err or err:match('^EEXIST:') then
|
if not err or err:match("^EEXIST:") then
|
||||||
cb()
|
cb()
|
||||||
else
|
else
|
||||||
cb(err)
|
cb(err)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
elseif action.entry_type == 'link' and action.link then
|
elseif action.entry_type == "link" and action.link then
|
||||||
local flags = nil
|
local flags = nil
|
||||||
local target = fs.posix_to_os_path(action.link)
|
local target = fs.posix_to_os_path(action.link)
|
||||||
if fs.is_windows then
|
if fs.is_windows then
|
||||||
|
|
@ -603,21 +600,9 @@ M.perform_action = function(action, cb)
|
||||||
---@diagnostic disable-next-line: param-type-mismatch
|
---@diagnostic disable-next-line: param-type-mismatch
|
||||||
uv.fs_symlink(target, path, flags, cb)
|
uv.fs_symlink(target, path, flags, cb)
|
||||||
else
|
else
|
||||||
fs.touch(
|
fs.touch(path, config.new_file_mode, cb)
|
||||||
path,
|
|
||||||
config.new_file_mode,
|
|
||||||
vim.schedule_wrap(function(err)
|
|
||||||
if not err then
|
|
||||||
vim.api.nvim_exec_autocmds(
|
|
||||||
'User',
|
|
||||||
{ pattern = 'CanolaFileCreated', modeline = false, data = { path = path } }
|
|
||||||
)
|
|
||||||
end
|
|
||||||
cb(err)
|
|
||||||
end)
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
elseif action.type == 'delete' then
|
elseif action.type == "delete" then
|
||||||
local _, path = util.parse_url(action.url)
|
local _, path = util.parse_url(action.url)
|
||||||
assert(path)
|
assert(path)
|
||||||
path = fs.posix_to_os_path(path)
|
path = fs.posix_to_os_path(path)
|
||||||
|
|
@ -634,11 +619,11 @@ M.perform_action = function(action, cb)
|
||||||
end
|
end
|
||||||
|
|
||||||
if config.delete_to_trash then
|
if config.delete_to_trash then
|
||||||
require('canola.adapters.trash').delete_to_trash(path, cb)
|
require("oil.adapters.trash").delete_to_trash(path, cb)
|
||||||
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 = assert(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)
|
||||||
|
|
@ -656,7 +641,7 @@ M.perform_action = function(action, cb)
|
||||||
-- We should never hit this because we don't implement supported_cross_adapter_actions
|
-- We should never hit this because we don't implement supported_cross_adapter_actions
|
||||||
cb("files adapter doesn't support cross-adapter move")
|
cb("files adapter doesn't support cross-adapter move")
|
||||||
end
|
end
|
||||||
elseif action.type == 'copy' then
|
elseif action.type == "copy" then
|
||||||
local dest_adapter = assert(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)
|
||||||
|
|
@ -671,7 +656,7 @@ M.perform_action = function(action, cb)
|
||||||
cb("files adapter doesn't support cross-adapter copy")
|
cb("files adapter doesn't support cross-adapter copy")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
cb(string.format('Bad action type: %s', action.type))
|
cb(string.format("Bad action type: %s", action.type))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -4,7 +4,7 @@ local M = {}
|
||||||
---@param num integer
|
---@param num integer
|
||||||
---@return string
|
---@return string
|
||||||
local function perm_to_str(exe_modifier, num)
|
local function perm_to_str(exe_modifier, num)
|
||||||
local str = (bit.band(num, 4) ~= 0 and 'r' or '-') .. (bit.band(num, 2) ~= 0 and 'w' or '-')
|
local str = (bit.band(num, 4) ~= 0 and "r" or "-") .. (bit.band(num, 2) ~= 0 and "w" or "-")
|
||||||
if exe_modifier then
|
if exe_modifier then
|
||||||
if bit.band(num, 1) ~= 0 then
|
if bit.band(num, 1) ~= 0 then
|
||||||
return str .. exe_modifier
|
return str .. exe_modifier
|
||||||
|
|
@ -12,7 +12,7 @@ local function perm_to_str(exe_modifier, num)
|
||||||
return str .. exe_modifier:upper()
|
return str .. exe_modifier:upper()
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
return str .. (bit.band(num, 1) ~= 0 and 'x' or '-')
|
return str .. (bit.band(num, 1) ~= 0 and "x" or "-")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -20,9 +20,9 @@ end
|
||||||
---@return string
|
---@return string
|
||||||
M.mode_to_str = function(mode)
|
M.mode_to_str = function(mode)
|
||||||
local extra = bit.rshift(mode, 9)
|
local extra = bit.rshift(mode, 9)
|
||||||
return perm_to_str(bit.band(extra, 4) ~= 0 and 's', bit.rshift(mode, 6))
|
return perm_to_str(bit.band(extra, 4) ~= 0 and "s", bit.rshift(mode, 6))
|
||||||
.. perm_to_str(bit.band(extra, 2) ~= 0 and 's', bit.rshift(mode, 3))
|
.. perm_to_str(bit.band(extra, 2) ~= 0 and "s", bit.rshift(mode, 3))
|
||||||
.. perm_to_str(bit.band(extra, 1) ~= 0 and 't', mode)
|
.. perm_to_str(bit.band(extra, 1) ~= 0 and "t", mode)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param mode integer
|
---@param mode integer
|
||||||
|
|
@ -38,25 +38,25 @@ end
|
||||||
---@param str string String of 3 characters
|
---@param str string String of 3 characters
|
||||||
---@return nil|integer
|
---@return nil|integer
|
||||||
local function str_to_mode(str)
|
local function str_to_mode(str)
|
||||||
local r, w, x = unpack(vim.split(str, '', {}))
|
local r, w, x = unpack(vim.split(str, "", {}))
|
||||||
local mode = 0
|
local mode = 0
|
||||||
if r == 'r' then
|
if r == "r" then
|
||||||
mode = bit.bor(mode, 4)
|
mode = bit.bor(mode, 4)
|
||||||
elseif r ~= '-' then
|
elseif r ~= "-" then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
if w == 'w' then
|
if w == "w" then
|
||||||
mode = bit.bor(mode, 2)
|
mode = bit.bor(mode, 2)
|
||||||
elseif w ~= '-' then
|
elseif w ~= "-" then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
-- t means sticky and executable
|
-- t means sticky and executable
|
||||||
-- T means sticky, not executable
|
-- T means sticky, not executable
|
||||||
-- s means setuid/setgid and executable
|
-- s means setuid/setgid and executable
|
||||||
-- S means setuid/setgid and not executable
|
-- S means setuid/setgid and not executable
|
||||||
if x == 'x' or x == 't' or x == 's' then
|
if x == "x" or x == "t" or x == "s" then
|
||||||
mode = bit.bor(mode, 1)
|
mode = bit.bor(mode, 1)
|
||||||
elseif x ~= '-' and x ~= 'T' and x ~= 'S' then
|
elseif x ~= "-" and x ~= "T" and x ~= "S" then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
return mode
|
return mode
|
||||||
|
|
@ -67,13 +67,13 @@ end
|
||||||
local function parse_extra_bits(perm)
|
local function parse_extra_bits(perm)
|
||||||
perm = perm:lower()
|
perm = perm:lower()
|
||||||
local mode = 0
|
local mode = 0
|
||||||
if perm:sub(3, 3) == 's' then
|
if perm:sub(3, 3) == "s" then
|
||||||
mode = bit.bor(mode, 4)
|
mode = bit.bor(mode, 4)
|
||||||
end
|
end
|
||||||
if perm:sub(6, 6) == 's' then
|
if perm:sub(6, 6) == "s" then
|
||||||
mode = bit.bor(mode, 2)
|
mode = bit.bor(mode, 2)
|
||||||
end
|
end
|
||||||
if perm:sub(9, 9) == 't' then
|
if perm:sub(9, 9) == "t" then
|
||||||
mode = bit.bor(mode, 1)
|
mode = bit.bor(mode, 1)
|
||||||
end
|
end
|
||||||
return mode
|
return mode
|
||||||
|
|
@ -83,7 +83,7 @@ end
|
||||||
---@return nil|integer
|
---@return nil|integer
|
||||||
---@return nil|string
|
---@return nil|string
|
||||||
M.parse = function(line)
|
M.parse = function(line)
|
||||||
local strval, rem = line:match('^([r%-][w%-][xsS%-][r%-][w%-][xsS%-][r%-][w%-][xtT%-])%s*(.*)$')
|
local strval, rem = line:match("^([r%-][w%-][xsS%-][r%-][w%-][xsS%-][r%-][w%-][xtT%-])%s*(.*)$")
|
||||||
if not strval then
|
if not strval then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
@ -1,79 +1,78 @@
|
||||||
local config = require('canola.config')
|
local config = require("oil.config")
|
||||||
local constants = require('canola.constants')
|
local constants = require("oil.constants")
|
||||||
local files = require('canola.adapters.files')
|
local files = require("oil.adapters.files")
|
||||||
local fs = require('canola.fs')
|
local fs = require("oil.fs")
|
||||||
local loading = require('canola.loading')
|
local loading = require("oil.loading")
|
||||||
local pathutil = require('canola.pathutil')
|
local pathutil = require("oil.pathutil")
|
||||||
local s3fs = require('canola.adapters.s3.s3fs')
|
local s3fs = require("oil.adapters.s3.s3fs")
|
||||||
local util = require('canola.util')
|
local util = require("oil.util")
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local FIELD_TYPE = constants.FIELD_TYPE
|
|
||||||
local FIELD_META = constants.FIELD_META
|
local FIELD_META = constants.FIELD_META
|
||||||
|
|
||||||
---@class (exact) canola.s3Url
|
---@class (exact) oil.s3Url
|
||||||
---@field scheme string
|
---@field scheme string
|
||||||
---@field bucket nil|string
|
---@field bucket nil|string
|
||||||
---@field path nil|string
|
---@field path nil|string
|
||||||
|
|
||||||
---@param canola_url string
|
---@param oil_url string
|
||||||
---@return canola.s3Url
|
---@return oil.s3Url
|
||||||
M.parse_url = function(canola_url)
|
M.parse_url = function(oil_url)
|
||||||
local scheme, url = util.parse_url(canola_url)
|
local scheme, url = util.parse_url(oil_url)
|
||||||
assert(scheme and url, string.format("Malformed input url '%s'", canola_url))
|
assert(scheme and url, string.format("Malformed input url '%s'", oil_url))
|
||||||
local ret = { scheme = scheme }
|
local ret = { scheme = scheme }
|
||||||
local bucket, path = url:match('^([^/]+)/?(.*)$')
|
local bucket, path = url:match("^([^/]+)/?(.*)$")
|
||||||
ret.bucket = bucket
|
ret.bucket = bucket
|
||||||
ret.path = path ~= '' and path or nil
|
ret.path = path ~= "" and path or nil
|
||||||
if not ret.bucket and ret.path then
|
if not ret.bucket and ret.path then
|
||||||
error(string.format('Parsing error for s3 url: %s', canola_url))
|
error(string.format("Parsing error for s3 url: %s", oil_url))
|
||||||
end
|
end
|
||||||
---@cast ret canola.s3Url
|
---@cast ret oil.s3Url
|
||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param url canola.s3Url
|
---@param url oil.s3Url
|
||||||
---@return string
|
---@return string
|
||||||
local function url_to_str(url)
|
local function url_to_str(url)
|
||||||
local pieces = { url.scheme }
|
local pieces = { url.scheme }
|
||||||
if url.bucket then
|
if url.bucket then
|
||||||
assert(url.bucket ~= '')
|
assert(url.bucket ~= "")
|
||||||
table.insert(pieces, url.bucket)
|
table.insert(pieces, url.bucket)
|
||||||
table.insert(pieces, '/')
|
table.insert(pieces, "/")
|
||||||
end
|
end
|
||||||
if url.path then
|
if url.path then
|
||||||
assert(url.path ~= '')
|
assert(url.path ~= "")
|
||||||
table.insert(pieces, url.path)
|
table.insert(pieces, url.path)
|
||||||
end
|
end
|
||||||
return table.concat(pieces, '')
|
return table.concat(pieces, "")
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param url canola.s3Url
|
---@param url oil.s3Url
|
||||||
---@param is_folder boolean
|
---@param is_folder boolean
|
||||||
---@return string
|
---@return string
|
||||||
local function url_to_s3(url, is_folder)
|
local function url_to_s3(url, is_folder)
|
||||||
local pieces = { 's3://' }
|
local pieces = { "s3://" }
|
||||||
if url.bucket then
|
if url.bucket then
|
||||||
assert(url.bucket ~= '')
|
assert(url.bucket ~= "")
|
||||||
table.insert(pieces, url.bucket)
|
table.insert(pieces, url.bucket)
|
||||||
table.insert(pieces, '/')
|
table.insert(pieces, "/")
|
||||||
end
|
end
|
||||||
if url.path then
|
if url.path then
|
||||||
assert(url.path ~= '')
|
assert(url.path ~= "")
|
||||||
table.insert(pieces, url.path)
|
table.insert(pieces, url.path)
|
||||||
if is_folder and not vim.endswith(url.path, '/') then
|
if is_folder and not vim.endswith(url.path, "/") then
|
||||||
table.insert(pieces, '/')
|
table.insert(pieces, "/")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return table.concat(pieces, '')
|
return table.concat(pieces, "")
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param url canola.s3Url
|
---@param url oil.s3Url
|
||||||
---@return boolean
|
---@return boolean
|
||||||
local function is_bucket(url)
|
local function is_bucket(url)
|
||||||
assert(url.bucket and url.bucket ~= '')
|
assert(url.bucket and url.bucket ~= "")
|
||||||
if url.path then
|
if url.path then
|
||||||
assert(url.path ~= '')
|
assert(url.path ~= "")
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
return true
|
return true
|
||||||
|
|
@ -84,24 +83,20 @@ s3_columns.size = {
|
||||||
render = function(entry, conf)
|
render = function(entry, conf)
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
if not meta or not meta.size then
|
if not meta or not meta.size then
|
||||||
return ''
|
return ""
|
||||||
end
|
elseif meta.size >= 1e9 then
|
||||||
if entry[FIELD_TYPE] == 'directory' then
|
return string.format("%.1fG", meta.size / 1e9)
|
||||||
return ''
|
|
||||||
end
|
|
||||||
if meta.size >= 1e9 then
|
|
||||||
return string.format('%.1fG', meta.size / 1e9)
|
|
||||||
elseif meta.size >= 1e6 then
|
elseif meta.size >= 1e6 then
|
||||||
return string.format('%.1fM', meta.size / 1e6)
|
return string.format("%.1fM", meta.size / 1e6)
|
||||||
elseif meta.size >= 1e3 then
|
elseif meta.size >= 1e3 then
|
||||||
return string.format('%.1fk', meta.size / 1e3)
|
return string.format("%.1fk", meta.size / 1e3)
|
||||||
else
|
else
|
||||||
return string.format('%d', meta.size)
|
return string.format("%d", meta.size)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
parse = function(line, conf)
|
parse = function(line, conf)
|
||||||
return line:match('^(%d+%S*)%s+(.*)$')
|
return line:match("^(%d+%S*)%s+(.*)$")
|
||||||
end,
|
end,
|
||||||
|
|
||||||
get_sort_value = function(entry)
|
get_sort_value = function(entry)
|
||||||
|
|
@ -118,21 +113,21 @@ s3_columns.birthtime = {
|
||||||
render = function(entry, conf)
|
render = function(entry, conf)
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
if not meta or not meta.date then
|
if not meta or not meta.date then
|
||||||
return ''
|
return ""
|
||||||
else
|
else
|
||||||
return meta.date
|
return meta.date
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
parse = function(line, conf)
|
parse = function(line, conf)
|
||||||
return line:match('^(%d+%-%d+%-%d+%s%d+:%d+:%d+)%s+(.*)$')
|
return line:match("^(%d+%-%d+%-%d+%s%d+:%d+:%d+)%s+(.*)$")
|
||||||
end,
|
end,
|
||||||
|
|
||||||
get_sort_value = function(entry)
|
get_sort_value = function(entry)
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
if meta and meta.date then
|
if meta and meta.date then
|
||||||
local year, month, day, hour, min, sec =
|
local year, month, day, hour, min, sec =
|
||||||
meta.date:match('^(%d+)%-(%d+)%-(%d+)%s(%d+):(%d+):(%d+)$')
|
meta.date:match("^(%d+)%-(%d+)%-(%d+)%s(%d+):(%d+):(%d+)$")
|
||||||
local time =
|
local time =
|
||||||
os.time({ year = year, month = month, day = day, hour = hour, min = min, sec = sec })
|
os.time({ year = year, month = month, day = day, hour = hour, min = min, sec = sec })
|
||||||
return time
|
return time
|
||||||
|
|
@ -143,7 +138,7 @@ s3_columns.birthtime = {
|
||||||
}
|
}
|
||||||
|
|
||||||
---@param name string
|
---@param name string
|
||||||
---@return nil|canola.ColumnDefinition
|
---@return nil|oil.ColumnDefinition
|
||||||
M.get_column = function(name)
|
M.get_column = function(name)
|
||||||
return s3_columns[name]
|
return s3_columns[name]
|
||||||
end
|
end
|
||||||
|
|
@ -153,9 +148,9 @@ end
|
||||||
M.get_parent = function(bufname)
|
M.get_parent = function(bufname)
|
||||||
local res = M.parse_url(bufname)
|
local res = M.parse_url(bufname)
|
||||||
if res.path then
|
if res.path then
|
||||||
assert(res.path ~= '')
|
assert(res.path ~= "")
|
||||||
local path = pathutil.parent(res.path)
|
local path = pathutil.parent(res.path)
|
||||||
res.path = path ~= '' and path or nil
|
res.path = path ~= "" and path or nil
|
||||||
else
|
else
|
||||||
res.bucket = nil
|
res.bucket = nil
|
||||||
end
|
end
|
||||||
|
|
@ -171,10 +166,10 @@ end
|
||||||
|
|
||||||
---@param url string
|
---@param url string
|
||||||
---@param column_defs string[]
|
---@param column_defs string[]
|
||||||
---@param callback fun(err?: string, entries?: canola.InternalEntry[], fetch_more?: fun())
|
---@param callback fun(err?: string, entries?: oil.InternalEntry[], fetch_more?: fun())
|
||||||
M.list = function(url, column_defs, callback)
|
M.list = function(url, column_defs, callback)
|
||||||
if vim.fn.executable('aws') ~= 1 then
|
if vim.fn.executable("aws") ~= 1 then
|
||||||
callback('`aws` is not executable. Can you run `aws s3 ls`?')
|
callback("`aws` is not executable. Can you run `aws s3 ls`?")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -189,19 +184,19 @@ M.is_modifiable = function(bufnr)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param action canola.Action
|
---@param action oil.Action
|
||||||
---@return string
|
---@return string
|
||||||
M.render_action = function(action)
|
M.render_action = function(action)
|
||||||
local is_folder = action.entry_type == 'directory'
|
local is_folder = action.entry_type == "directory"
|
||||||
if action.type == 'create' then
|
if action.type == "create" then
|
||||||
local res = M.parse_url(action.url)
|
local res = M.parse_url(action.url)
|
||||||
local extra = is_bucket(res) and 'BUCKET ' or ''
|
local extra = is_bucket(res) and "BUCKET " or ""
|
||||||
return string.format('CREATE %s%s', extra, url_to_s3(res, is_folder))
|
return string.format("CREATE %s%s", extra, url_to_s3(res, is_folder))
|
||||||
elseif action.type == 'delete' then
|
elseif action.type == "delete" then
|
||||||
local res = M.parse_url(action.url)
|
local res = M.parse_url(action.url)
|
||||||
local extra = is_bucket(res) and 'BUCKET ' or ''
|
local extra = is_bucket(res) and "BUCKET " or ""
|
||||||
return string.format('DELETE %s%s', extra, url_to_s3(res, is_folder))
|
return string.format("DELETE %s%s", extra, url_to_s3(res, is_folder))
|
||||||
elseif action.type == 'move' or action.type == 'copy' then
|
elseif action.type == "move" or action.type == "copy" then
|
||||||
local src = action.src_url
|
local src = action.src_url
|
||||||
local dest = action.dest_url
|
local dest = action.dest_url
|
||||||
if config.get_adapter_by_scheme(src) ~= M then
|
if config.get_adapter_by_scheme(src) ~= M then
|
||||||
|
|
@ -215,39 +210,39 @@ M.render_action = function(action)
|
||||||
dest = files.to_short_os_path(path, action.entry_type)
|
dest = files.to_short_os_path(path, action.entry_type)
|
||||||
src = url_to_s3(M.parse_url(src), is_folder)
|
src = url_to_s3(M.parse_url(src), is_folder)
|
||||||
end
|
end
|
||||||
return string.format(' %s %s -> %s', action.type:upper(), src, dest)
|
return string.format(" %s %s -> %s", action.type:upper(), src, dest)
|
||||||
else
|
else
|
||||||
error(string.format("Bad action type: '%s'", action.type))
|
error(string.format("Bad action type: '%s'", action.type))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param action canola.Action
|
---@param action oil.Action
|
||||||
---@param cb fun(err: nil|string)
|
---@param cb fun(err: nil|string)
|
||||||
M.perform_action = function(action, cb)
|
M.perform_action = function(action, cb)
|
||||||
local is_folder = action.entry_type == 'directory'
|
local is_folder = action.entry_type == "directory"
|
||||||
if action.type == 'create' then
|
if action.type == "create" then
|
||||||
local res = M.parse_url(action.url)
|
local res = M.parse_url(action.url)
|
||||||
local bucket = is_bucket(res)
|
local bucket = is_bucket(res)
|
||||||
|
|
||||||
if action.entry_type == 'directory' and bucket then
|
if action.entry_type == "directory" and bucket then
|
||||||
s3fs.mb(url_to_s3(res, true), cb)
|
s3fs.mb(url_to_s3(res, true), cb)
|
||||||
elseif action.entry_type == 'directory' or action.entry_type == 'file' then
|
elseif action.entry_type == "directory" or action.entry_type == "file" then
|
||||||
s3fs.touch(url_to_s3(res, is_folder), cb)
|
s3fs.touch(url_to_s3(res, is_folder), cb)
|
||||||
else
|
else
|
||||||
cb(string.format('Bad entry type on s3 create action: %s', action.entry_type))
|
cb(string.format("Bad entry type on s3 create action: %s", action.entry_type))
|
||||||
end
|
end
|
||||||
elseif action.type == 'delete' then
|
elseif action.type == "delete" then
|
||||||
local res = M.parse_url(action.url)
|
local res = M.parse_url(action.url)
|
||||||
local bucket = is_bucket(res)
|
local bucket = is_bucket(res)
|
||||||
|
|
||||||
if action.entry_type == 'directory' and bucket then
|
if action.entry_type == "directory" and bucket then
|
||||||
s3fs.rb(url_to_s3(res, true), cb)
|
s3fs.rb(url_to_s3(res, true), cb)
|
||||||
elseif action.entry_type == 'directory' or action.entry_type == 'file' then
|
elseif action.entry_type == "directory" or action.entry_type == "file" then
|
||||||
s3fs.rm(url_to_s3(res, is_folder), is_folder, cb)
|
s3fs.rm(url_to_s3(res, is_folder), is_folder, cb)
|
||||||
else
|
else
|
||||||
cb(string.format('Bad entry type on s3 delete action: %s', action.entry_type))
|
cb(string.format("Bad entry type on s3 delete action: %s", action.entry_type))
|
||||||
end
|
end
|
||||||
elseif action.type == 'move' then
|
elseif action.type == "move" then
|
||||||
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
||||||
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
||||||
if
|
if
|
||||||
|
|
@ -255,7 +250,7 @@ M.perform_action = function(action, cb)
|
||||||
then
|
then
|
||||||
cb(
|
cb(
|
||||||
string.format(
|
string.format(
|
||||||
'We should never attempt to move from the %s adapter to the %s adapter.',
|
"We should never attempt to move from the %s adapter to the %s adapter.",
|
||||||
src_adapter.name,
|
src_adapter.name,
|
||||||
dest_adapter.name
|
dest_adapter.name
|
||||||
)
|
)
|
||||||
|
|
@ -281,7 +276,7 @@ M.perform_action = function(action, cb)
|
||||||
assert(dest)
|
assert(dest)
|
||||||
|
|
||||||
s3fs.mv(src, dest, is_folder, cb)
|
s3fs.mv(src, dest, is_folder, cb)
|
||||||
elseif action.type == 'copy' then
|
elseif action.type == "copy" then
|
||||||
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
||||||
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
||||||
if
|
if
|
||||||
|
|
@ -289,7 +284,7 @@ M.perform_action = function(action, cb)
|
||||||
then
|
then
|
||||||
cb(
|
cb(
|
||||||
string.format(
|
string.format(
|
||||||
'We should never attempt to copy from the %s adapter to the %s adapter.',
|
"We should never attempt to copy from the %s adapter to the %s adapter.",
|
||||||
src_adapter.name,
|
src_adapter.name,
|
||||||
dest_adapter.name
|
dest_adapter.name
|
||||||
)
|
)
|
||||||
|
|
@ -316,11 +311,11 @@ M.perform_action = function(action, cb)
|
||||||
|
|
||||||
s3fs.cp(src, dest, is_folder, cb)
|
s3fs.cp(src, dest, is_folder, cb)
|
||||||
else
|
else
|
||||||
cb(string.format('Bad action type: %s', action.type))
|
cb(string.format("Bad action type: %s", action.type))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
M.supported_cross_adapter_actions = { files = 'move' }
|
M.supported_cross_adapter_actions = { files = "move" }
|
||||||
|
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
M.read_file = function(bufnr)
|
M.read_file = function(bufnr)
|
||||||
|
|
@ -328,11 +323,11 @@ M.read_file = function(bufnr)
|
||||||
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
||||||
local url = M.parse_url(bufname)
|
local url = M.parse_url(bufname)
|
||||||
local basename = pathutil.basename(bufname)
|
local basename = pathutil.basename(bufname)
|
||||||
local cache_dir = vim.fn.stdpath('cache')
|
local cache_dir = vim.fn.stdpath("cache")
|
||||||
assert(type(cache_dir) == 'string')
|
assert(type(cache_dir) == "string")
|
||||||
local tmpdir = fs.join(cache_dir, 'canola')
|
local tmpdir = fs.join(cache_dir, "oil")
|
||||||
fs.mkdirp(tmpdir)
|
fs.mkdirp(tmpdir)
|
||||||
local fd, tmpfile = vim.loop.fs_mkstemp(fs.join(tmpdir, 's3_XXXXXX'))
|
local fd, tmpfile = vim.loop.fs_mkstemp(fs.join(tmpdir, "s3_XXXXXX"))
|
||||||
if fd then
|
if fd then
|
||||||
vim.loop.fs_close(fd)
|
vim.loop.fs_close(fd)
|
||||||
end
|
end
|
||||||
|
|
@ -341,9 +336,9 @@ M.read_file = function(bufnr)
|
||||||
s3fs.cp(url_to_s3(url, false), tmpfile, false, function(err)
|
s3fs.cp(url_to_s3(url, false), tmpfile, false, function(err)
|
||||||
loading.set_loading(bufnr, false)
|
loading.set_loading(bufnr, false)
|
||||||
vim.bo[bufnr].modifiable = true
|
vim.bo[bufnr].modifiable = true
|
||||||
vim.cmd.doautocmd({ args = { 'BufReadPre', bufname }, mods = { silent = true } })
|
vim.cmd.doautocmd({ args = { "BufReadPre", bufname }, mods = { silent = true } })
|
||||||
if err then
|
if err then
|
||||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, vim.split(err, '\n'))
|
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, vim.split(err, "\n"))
|
||||||
else
|
else
|
||||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, {})
|
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, {})
|
||||||
vim.api.nvim_buf_call(bufnr, function()
|
vim.api.nvim_buf_call(bufnr, function()
|
||||||
|
|
@ -357,7 +352,7 @@ M.read_file = function(bufnr)
|
||||||
if filetype then
|
if filetype then
|
||||||
vim.bo[bufnr].filetype = filetype
|
vim.bo[bufnr].filetype = filetype
|
||||||
end
|
end
|
||||||
vim.cmd.doautocmd({ args = { 'BufReadPost', bufname }, mods = { silent = true } })
|
vim.cmd.doautocmd({ args = { "BufReadPost", bufname }, mods = { silent = true } })
|
||||||
vim.api.nvim_buf_delete(tmp_bufnr, { force = true })
|
vim.api.nvim_buf_delete(tmp_bufnr, { force = true })
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
@ -366,14 +361,14 @@ end
|
||||||
M.write_file = function(bufnr)
|
M.write_file = function(bufnr)
|
||||||
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
||||||
local url = M.parse_url(bufname)
|
local url = M.parse_url(bufname)
|
||||||
local cache_dir = vim.fn.stdpath('cache')
|
local cache_dir = vim.fn.stdpath("cache")
|
||||||
assert(type(cache_dir) == 'string')
|
assert(type(cache_dir) == "string")
|
||||||
local tmpdir = fs.join(cache_dir, 'canola')
|
local tmpdir = fs.join(cache_dir, "oil")
|
||||||
local fd, tmpfile = vim.loop.fs_mkstemp(fs.join(tmpdir, 's3_XXXXXXXX'))
|
local fd, tmpfile = vim.loop.fs_mkstemp(fs.join(tmpdir, "s3_XXXXXXXX"))
|
||||||
if fd then
|
if fd then
|
||||||
vim.loop.fs_close(fd)
|
vim.loop.fs_close(fd)
|
||||||
end
|
end
|
||||||
vim.cmd.doautocmd({ args = { 'BufWritePre', bufname }, mods = { silent = true } })
|
vim.cmd.doautocmd({ args = { "BufWritePre", bufname }, mods = { silent = true } })
|
||||||
vim.bo[bufnr].modifiable = false
|
vim.bo[bufnr].modifiable = false
|
||||||
vim.cmd.write({ args = { tmpfile }, bang = true, mods = { silent = true, noautocmd = true } })
|
vim.cmd.write({ args = { tmpfile }, bang = true, mods = { silent = true, noautocmd = true } })
|
||||||
local tmp_bufnr = vim.fn.bufadd(tmpfile)
|
local tmp_bufnr = vim.fn.bufadd(tmpfile)
|
||||||
|
|
@ -381,10 +376,10 @@ M.write_file = function(bufnr)
|
||||||
s3fs.cp(tmpfile, url_to_s3(url, false), false, function(err)
|
s3fs.cp(tmpfile, url_to_s3(url, false), false, function(err)
|
||||||
vim.bo[bufnr].modifiable = true
|
vim.bo[bufnr].modifiable = true
|
||||||
if err then
|
if err then
|
||||||
vim.notify(string.format('Error writing file: %s', err), vim.log.levels.ERROR)
|
vim.notify(string.format("Error writing file: %s", err), vim.log.levels.ERROR)
|
||||||
else
|
else
|
||||||
vim.bo[bufnr].modified = false
|
vim.bo[bufnr].modified = false
|
||||||
vim.cmd.doautocmd({ args = { 'BufWritePost', bufname }, mods = { silent = true } })
|
vim.cmd.doautocmd({ args = { "BufWritePost", bufname }, mods = { silent = true } })
|
||||||
end
|
end
|
||||||
vim.loop.fs_unlink(tmpfile)
|
vim.loop.fs_unlink(tmpfile)
|
||||||
vim.api.nvim_buf_delete(tmp_bufnr, { force = true })
|
vim.api.nvim_buf_delete(tmp_bufnr, { force = true })
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
local cache = require('canola.cache')
|
local cache = require("oil.cache")
|
||||||
local config = require('canola.config')
|
local config = require("oil.config")
|
||||||
local constants = require('canola.constants')
|
local constants = require("oil.constants")
|
||||||
local shell = require('canola.shell')
|
local shell = require("oil.shell")
|
||||||
local util = require('canola.util')
|
local util = require("oil.util")
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
|
|
@ -10,35 +10,35 @@ local FIELD_META = constants.FIELD_META
|
||||||
|
|
||||||
---@param line string
|
---@param line string
|
||||||
---@return string Name of entry
|
---@return string Name of entry
|
||||||
---@return canola.EntryType
|
---@return oil.EntryType
|
||||||
---@return table Metadata for entry
|
---@return table Metadata for entry
|
||||||
local function parse_ls_line_bucket(line)
|
local function parse_ls_line_bucket(line)
|
||||||
local date, name = line:match('^(%d+%-%d+%-%d+%s%d+:%d+:%d+)%s+(.*)$')
|
local date, name = line:match("^(%d+%-%d+%-%d+%s%d+:%d+:%d+)%s+(.*)$")
|
||||||
if not date or not name then
|
if not date or not name then
|
||||||
error(string.format("Could not parse '%s'", line))
|
error(string.format("Could not parse '%s'", line))
|
||||||
end
|
end
|
||||||
local type = 'directory'
|
local type = "directory"
|
||||||
local meta = { date = date }
|
local meta = { date = date }
|
||||||
return name, type, meta
|
return name, type, meta
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param line string
|
---@param line string
|
||||||
---@return string Name of entry
|
---@return string Name of entry
|
||||||
---@return canola.EntryType
|
---@return oil.EntryType
|
||||||
---@return table Metadata for entry
|
---@return table Metadata for entry
|
||||||
local function parse_ls_line_file(line)
|
local function parse_ls_line_file(line)
|
||||||
local name = line:match('^%s+PRE%s+(.*)/$')
|
local name = line:match("^%s+PRE%s+(.*)/$")
|
||||||
local type = 'directory'
|
local type = "directory"
|
||||||
local meta = {}
|
local meta = {}
|
||||||
if name then
|
if name then
|
||||||
return name, type, meta
|
return name, type, meta
|
||||||
end
|
end
|
||||||
local date, size
|
local date, size
|
||||||
date, size, name = line:match('^(%d+%-%d+%-%d+%s%d+:%d+:%d+)%s+(%d+)%s+(.*)$')
|
date, size, name = line:match("^(%d+%-%d+%-%d+%s%d+:%d+:%d+)%s+(%d+)%s+(.*)$")
|
||||||
if not name then
|
if not name then
|
||||||
error(string.format("Could not parse '%s'", line))
|
error(string.format("Could not parse '%s'", line))
|
||||||
end
|
end
|
||||||
type = 'file'
|
type = "file"
|
||||||
meta = { date = date, size = tonumber(size) }
|
meta = { date = date, size = tonumber(size) }
|
||||||
return name, type, meta
|
return name, type, meta
|
||||||
end
|
end
|
||||||
|
|
@ -46,15 +46,15 @@ end
|
||||||
---@param cmd string[] cmd and flags
|
---@param cmd string[] cmd and flags
|
||||||
---@return string[] Shell command to run
|
---@return string[] Shell command to run
|
||||||
local function create_s3_command(cmd)
|
local function create_s3_command(cmd)
|
||||||
local full_cmd = vim.list_extend({ 'aws', 's3' }, cmd)
|
local full_cmd = vim.list_extend({ "aws", "s3" }, cmd)
|
||||||
return vim.list_extend(full_cmd, config.extra_s3_args)
|
return vim.list_extend(full_cmd, config.extra_s3_args)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param url string
|
---@param url string
|
||||||
---@param path string
|
---@param path string
|
||||||
---@param callback fun(err?: string, entries?: canola.InternalEntry[], fetch_more?: fun())
|
---@param callback fun(err?: string, entries?: oil.InternalEntry[], fetch_more?: fun())
|
||||||
function M.list_dir(url, path, callback)
|
function M.list_dir(url, path, callback)
|
||||||
local cmd = create_s3_command({ 'ls', path, '--color=off', '--no-cli-pager' })
|
local cmd = create_s3_command({ "ls", path, "--color=off", "--no-cli-pager" })
|
||||||
shell.run(cmd, function(err, lines)
|
shell.run(cmd, function(err, lines)
|
||||||
if err then
|
if err then
|
||||||
return callback(err)
|
return callback(err)
|
||||||
|
|
@ -63,13 +63,13 @@ function M.list_dir(url, path, callback)
|
||||||
local cache_entries = {}
|
local cache_entries = {}
|
||||||
local url_path, _
|
local url_path, _
|
||||||
_, url_path = util.parse_url(url)
|
_, url_path = util.parse_url(url)
|
||||||
local is_top_level = url_path == nil or url_path:match('/') == nil
|
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
|
local parse_ls_line = is_top_level and parse_ls_line_bucket or parse_ls_line_file
|
||||||
for _, line in ipairs(lines) do
|
for _, line in ipairs(lines) do
|
||||||
if line ~= '' then
|
if line ~= "" then
|
||||||
local name, type, meta = parse_ls_line(line)
|
local name, type, meta = parse_ls_line(line)
|
||||||
-- in s3 '-' can be used to create an "empty folder"
|
-- in s3 '-' can be used to create an "empty folder"
|
||||||
if name ~= '-' then
|
if name ~= "-" then
|
||||||
local cache_entry = cache.create_entry(url, name, type)
|
local cache_entry = cache.create_entry(url, name, type)
|
||||||
table.insert(cache_entries, cache_entry)
|
table.insert(cache_entries, cache_entry)
|
||||||
cache_entry[FIELD_META] = meta
|
cache_entry[FIELD_META] = meta
|
||||||
|
|
@ -85,8 +85,8 @@ end
|
||||||
---@param callback fun(err: nil|string)
|
---@param callback fun(err: nil|string)
|
||||||
function M.touch(path, callback)
|
function M.touch(path, callback)
|
||||||
-- here "-" means that we copy from stdin
|
-- here "-" means that we copy from stdin
|
||||||
local cmd = create_s3_command({ 'cp', '-', path })
|
local cmd = create_s3_command({ "cp", "-", path })
|
||||||
shell.run(cmd, { stdin = 'null' }, callback)
|
shell.run(cmd, { stdin = "null" }, callback)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Remove files
|
--- Remove files
|
||||||
|
|
@ -94,9 +94,9 @@ end
|
||||||
---@param is_folder boolean
|
---@param is_folder boolean
|
||||||
---@param callback fun(err: nil|string)
|
---@param callback fun(err: nil|string)
|
||||||
function M.rm(path, is_folder, callback)
|
function M.rm(path, is_folder, callback)
|
||||||
local main_cmd = { 'rm', path }
|
local main_cmd = { "rm", path }
|
||||||
if is_folder then
|
if is_folder then
|
||||||
table.insert(main_cmd, '--recursive')
|
table.insert(main_cmd, "--recursive")
|
||||||
end
|
end
|
||||||
local cmd = create_s3_command(main_cmd)
|
local cmd = create_s3_command(main_cmd)
|
||||||
shell.run(cmd, callback)
|
shell.run(cmd, callback)
|
||||||
|
|
@ -106,7 +106,7 @@ end
|
||||||
---@param bucket string
|
---@param bucket string
|
||||||
---@param callback fun(err: nil|string)
|
---@param callback fun(err: nil|string)
|
||||||
function M.rb(bucket, callback)
|
function M.rb(bucket, callback)
|
||||||
local cmd = create_s3_command({ 'rb', bucket })
|
local cmd = create_s3_command({ "rb", bucket })
|
||||||
shell.run(cmd, callback)
|
shell.run(cmd, callback)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -114,7 +114,7 @@ end
|
||||||
---@param bucket string
|
---@param bucket string
|
||||||
---@param callback fun(err: nil|string)
|
---@param callback fun(err: nil|string)
|
||||||
function M.mb(bucket, callback)
|
function M.mb(bucket, callback)
|
||||||
local cmd = create_s3_command({ 'mb', bucket })
|
local cmd = create_s3_command({ "mb", bucket })
|
||||||
shell.run(cmd, callback)
|
shell.run(cmd, callback)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -124,9 +124,9 @@ end
|
||||||
---@param is_folder boolean
|
---@param is_folder boolean
|
||||||
---@param callback fun(err: nil|string)
|
---@param callback fun(err: nil|string)
|
||||||
function M.mv(src, dest, is_folder, callback)
|
function M.mv(src, dest, is_folder, callback)
|
||||||
local main_cmd = { 'mv', src, dest }
|
local main_cmd = { "mv", src, dest }
|
||||||
if is_folder then
|
if is_folder then
|
||||||
table.insert(main_cmd, '--recursive')
|
table.insert(main_cmd, "--recursive")
|
||||||
end
|
end
|
||||||
local cmd = create_s3_command(main_cmd)
|
local cmd = create_s3_command(main_cmd)
|
||||||
shell.run(cmd, callback)
|
shell.run(cmd, callback)
|
||||||
|
|
@ -138,9 +138,9 @@ end
|
||||||
---@param is_folder boolean
|
---@param is_folder boolean
|
||||||
---@param callback fun(err: nil|string)
|
---@param callback fun(err: nil|string)
|
||||||
function M.cp(src, dest, is_folder, callback)
|
function M.cp(src, dest, is_folder, callback)
|
||||||
local main_cmd = { 'cp', src, dest }
|
local main_cmd = { "cp", src, dest }
|
||||||
if is_folder then
|
if is_folder then
|
||||||
table.insert(main_cmd, '--recursive')
|
table.insert(main_cmd, "--recursive")
|
||||||
end
|
end
|
||||||
local cmd = create_s3_command(main_cmd)
|
local cmd = create_s3_command(main_cmd)
|
||||||
shell.run(cmd, callback)
|
shell.run(cmd, callback)
|
||||||
|
|
@ -1,20 +1,19 @@
|
||||||
local config = require('canola.config')
|
local config = require("oil.config")
|
||||||
local constants = require('canola.constants')
|
local constants = require("oil.constants")
|
||||||
local files = require('canola.adapters.files')
|
local files = require("oil.adapters.files")
|
||||||
local fs = require('canola.fs')
|
local fs = require("oil.fs")
|
||||||
local loading = require('canola.loading')
|
local loading = require("oil.loading")
|
||||||
local pathutil = require('canola.pathutil')
|
local pathutil = require("oil.pathutil")
|
||||||
local permissions = require('canola.adapters.files.permissions')
|
local permissions = require("oil.adapters.files.permissions")
|
||||||
local shell = require('canola.shell')
|
local shell = require("oil.shell")
|
||||||
local sshfs = require('canola.adapters.ssh.sshfs')
|
local sshfs = require("oil.adapters.ssh.sshfs")
|
||||||
local util = require('canola.util')
|
local util = require("oil.util")
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local FIELD_NAME = constants.FIELD_NAME
|
local FIELD_NAME = constants.FIELD_NAME
|
||||||
local FIELD_TYPE = constants.FIELD_TYPE
|
|
||||||
local FIELD_META = constants.FIELD_META
|
local FIELD_META = constants.FIELD_META
|
||||||
|
|
||||||
---@class (exact) canola.sshUrl
|
---@class (exact) oil.sshUrl
|
||||||
---@field scheme string
|
---@field scheme string
|
||||||
---@field host string
|
---@field host string
|
||||||
---@field user nil|string
|
---@field user nil|string
|
||||||
|
|
@ -23,75 +22,75 @@ local FIELD_META = constants.FIELD_META
|
||||||
|
|
||||||
---@param args string[]
|
---@param args string[]
|
||||||
local function scp(args, ...)
|
local function scp(args, ...)
|
||||||
local cmd = vim.list_extend({ 'scp', '-C' }, config.extra_scp_args)
|
local cmd = vim.list_extend({ "scp", "-C" }, config.extra_scp_args)
|
||||||
vim.list_extend(cmd, args)
|
vim.list_extend(cmd, args)
|
||||||
shell.run(cmd, ...)
|
shell.run(cmd, ...)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param canola_url string
|
---@param oil_url string
|
||||||
---@return canola.sshUrl
|
---@return oil.sshUrl
|
||||||
M.parse_url = function(canola_url)
|
M.parse_url = function(oil_url)
|
||||||
local scheme, url = util.parse_url(canola_url)
|
local scheme, url = util.parse_url(oil_url)
|
||||||
assert(scheme and url, string.format("Malformed input url '%s'", canola_url))
|
assert(scheme and url, string.format("Malformed input url '%s'", oil_url))
|
||||||
local ret = { scheme = scheme }
|
local ret = { scheme = scheme }
|
||||||
local username, rem = url:match('^([^@%s]+)@(.*)$')
|
local username, rem = url:match("^([^@%s]+)@(.*)$")
|
||||||
ret.user = username
|
ret.user = username
|
||||||
url = rem or url
|
url = rem or url
|
||||||
local host, port, path = url:match('^([^:]+):(%d+)/(.*)$')
|
local host, port, path = url:match("^([^:]+):(%d+)/(.*)$")
|
||||||
if host then
|
if host then
|
||||||
ret.host = host
|
ret.host = host
|
||||||
ret.port = tonumber(port)
|
ret.port = tonumber(port)
|
||||||
ret.path = path
|
ret.path = path
|
||||||
else
|
else
|
||||||
host, path = url:match('^([^/]+)/(.*)$')
|
host, path = url:match("^([^/]+)/(.*)$")
|
||||||
ret.host = host
|
ret.host = host
|
||||||
ret.path = path
|
ret.path = path
|
||||||
end
|
end
|
||||||
if not ret.host or not ret.path then
|
if not ret.host or not ret.path then
|
||||||
error(string.format('Malformed SSH url: %s', canola_url))
|
error(string.format("Malformed SSH url: %s", oil_url))
|
||||||
end
|
end
|
||||||
|
|
||||||
---@cast ret canola.sshUrl
|
---@cast ret oil.sshUrl
|
||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param url canola.sshUrl
|
---@param url oil.sshUrl
|
||||||
---@return string
|
---@return string
|
||||||
local function url_to_str(url)
|
local function url_to_str(url)
|
||||||
local pieces = { url.scheme }
|
local pieces = { url.scheme }
|
||||||
if url.user then
|
if url.user then
|
||||||
table.insert(pieces, url.user)
|
table.insert(pieces, url.user)
|
||||||
table.insert(pieces, '@')
|
table.insert(pieces, "@")
|
||||||
end
|
end
|
||||||
table.insert(pieces, url.host)
|
table.insert(pieces, url.host)
|
||||||
if url.port then
|
if url.port then
|
||||||
table.insert(pieces, string.format(':%d', url.port))
|
table.insert(pieces, string.format(":%d", url.port))
|
||||||
end
|
end
|
||||||
table.insert(pieces, '/')
|
table.insert(pieces, "/")
|
||||||
table.insert(pieces, url.path)
|
table.insert(pieces, url.path)
|
||||||
return table.concat(pieces, '')
|
return table.concat(pieces, "")
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param url canola.sshUrl
|
---@param url oil.sshUrl
|
||||||
---@return string
|
---@return string
|
||||||
local function url_to_scp(url)
|
local function url_to_scp(url)
|
||||||
local pieces = { 'scp://' }
|
local pieces = { "scp://" }
|
||||||
if url.user then
|
if url.user then
|
||||||
table.insert(pieces, url.user)
|
table.insert(pieces, url.user)
|
||||||
table.insert(pieces, '@')
|
table.insert(pieces, "@")
|
||||||
end
|
end
|
||||||
table.insert(pieces, url.host)
|
table.insert(pieces, url.host)
|
||||||
if url.port then
|
if url.port then
|
||||||
table.insert(pieces, string.format(':%d', url.port))
|
table.insert(pieces, string.format(":%d", url.port))
|
||||||
end
|
end
|
||||||
table.insert(pieces, '/')
|
table.insert(pieces, "/")
|
||||||
local escaped_path = util.url_escape(url.path)
|
local escaped_path = util.url_escape(url.path)
|
||||||
table.insert(pieces, escaped_path)
|
table.insert(pieces, escaped_path)
|
||||||
return table.concat(pieces, '')
|
return table.concat(pieces, "")
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param url1 canola.sshUrl
|
---@param url1 oil.sshUrl
|
||||||
---@param url2 canola.sshUrl
|
---@param url2 oil.sshUrl
|
||||||
---@return boolean
|
---@return boolean
|
||||||
local function url_hosts_equal(url1, url2)
|
local function url_hosts_equal(url1, url2)
|
||||||
return url1.host == url2.host and url1.port == url2.port and url1.user == url2.user
|
return url1.host == url2.host and url1.port == url2.port and url1.user == url2.user
|
||||||
|
|
@ -100,11 +99,11 @@ end
|
||||||
local _connections = {}
|
local _connections = {}
|
||||||
---@param url string
|
---@param url string
|
||||||
---@param allow_retry nil|boolean
|
---@param allow_retry nil|boolean
|
||||||
---@return canola.sshFs
|
---@return oil.sshFs
|
||||||
local function get_connection(url, allow_retry)
|
local function get_connection(url, allow_retry)
|
||||||
local res = M.parse_url(url)
|
local res = M.parse_url(url)
|
||||||
res.scheme = config.adapter_to_scheme.ssh
|
res.scheme = config.adapter_to_scheme.ssh
|
||||||
res.path = ''
|
res.path = ""
|
||||||
local key = url_to_str(res)
|
local key = url_to_str(res)
|
||||||
local conn = _connections[key]
|
local conn = _connections[key]
|
||||||
if not conn or (allow_retry and conn:get_connection_error()) then
|
if not conn or (allow_retry and conn:get_connection_error()) then
|
||||||
|
|
@ -138,7 +137,7 @@ ssh_columns.permissions = {
|
||||||
end,
|
end,
|
||||||
|
|
||||||
render_action = function(action)
|
render_action = function(action)
|
||||||
return string.format('CHMOD %s %s', permissions.mode_to_octal_str(action.value), action.url)
|
return string.format("CHMOD %s %s", permissions.mode_to_octal_str(action.value), action.url)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
perform_action = function(action, callback)
|
perform_action = function(action, callback)
|
||||||
|
|
@ -152,24 +151,20 @@ ssh_columns.size = {
|
||||||
render = function(entry, conf)
|
render = function(entry, conf)
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
if not meta or not meta.size then
|
if not meta or not meta.size then
|
||||||
return ''
|
return ""
|
||||||
end
|
elseif meta.size >= 1e9 then
|
||||||
if entry[FIELD_TYPE] == 'directory' then
|
return string.format("%.1fG", meta.size / 1e9)
|
||||||
return ''
|
|
||||||
end
|
|
||||||
if meta.size >= 1e9 then
|
|
||||||
return string.format('%.1fG', meta.size / 1e9)
|
|
||||||
elseif meta.size >= 1e6 then
|
elseif meta.size >= 1e6 then
|
||||||
return string.format('%.1fM', meta.size / 1e6)
|
return string.format("%.1fM", meta.size / 1e6)
|
||||||
elseif meta.size >= 1e3 then
|
elseif meta.size >= 1e3 then
|
||||||
return string.format('%.1fk', meta.size / 1e3)
|
return string.format("%.1fk", meta.size / 1e3)
|
||||||
else
|
else
|
||||||
return string.format('%d', meta.size)
|
return string.format("%d", meta.size)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
parse = function(line, conf)
|
parse = function(line, conf)
|
||||||
return line:match('^(%d+%S*)%s+(.*)$')
|
return line:match("^(%d+%S*)%s+(.*)$")
|
||||||
end,
|
end,
|
||||||
|
|
||||||
get_sort_value = function(entry)
|
get_sort_value = function(entry)
|
||||||
|
|
@ -183,7 +178,7 @@ ssh_columns.size = {
|
||||||
}
|
}
|
||||||
|
|
||||||
---@param name string
|
---@param name string
|
||||||
---@return nil|canola.ColumnDefinition
|
---@return nil|oil.ColumnDefinition
|
||||||
M.get_column = function(name)
|
M.get_column = function(name)
|
||||||
return ssh_columns[name]
|
return ssh_columns[name]
|
||||||
end
|
end
|
||||||
|
|
@ -211,13 +206,13 @@ M.normalize_url = function(url, callback)
|
||||||
local conn = get_connection(url, true)
|
local conn = get_connection(url, true)
|
||||||
|
|
||||||
local path = res.path
|
local path = res.path
|
||||||
if path == '' then
|
if path == "" then
|
||||||
path = '.'
|
path = "."
|
||||||
end
|
end
|
||||||
|
|
||||||
conn:realpath(path, function(err, abspath)
|
conn:realpath(path, function(err, abspath)
|
||||||
if err then
|
if err then
|
||||||
vim.notify(string.format('Error normalizing url %s: %s', url, err), vim.log.levels.WARN)
|
vim.notify(string.format("Error normalizing url %s: %s", url, err), vim.log.levels.WARN)
|
||||||
callback(url)
|
callback(url)
|
||||||
else
|
else
|
||||||
res.path = abspath
|
res.path = abspath
|
||||||
|
|
@ -228,7 +223,7 @@ end
|
||||||
|
|
||||||
---@param url string
|
---@param url string
|
||||||
---@param column_defs string[]
|
---@param column_defs string[]
|
||||||
---@param callback fun(err?: string, entries?: canola.InternalEntry[], fetch_more?: fun())
|
---@param callback fun(err?: string, entries?: oil.InternalEntry[], fetch_more?: fun())
|
||||||
M.list = function(url, column_defs, callback)
|
M.list = function(url, column_defs, callback)
|
||||||
local res = M.parse_url(url)
|
local res = M.parse_url(url)
|
||||||
|
|
||||||
|
|
@ -261,18 +256,18 @@ M.is_modifiable = function(bufnr)
|
||||||
return bit.band(rwx, 2) ~= 0
|
return bit.band(rwx, 2) ~= 0
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param action canola.Action
|
---@param action oil.Action
|
||||||
---@return string
|
---@return string
|
||||||
M.render_action = function(action)
|
M.render_action = function(action)
|
||||||
if action.type == 'create' then
|
if action.type == "create" then
|
||||||
local ret = string.format('CREATE %s', action.url)
|
local ret = string.format("CREATE %s", action.url)
|
||||||
if action.link then
|
if action.link then
|
||||||
ret = ret .. ' -> ' .. action.link
|
ret = ret .. " -> " .. action.link
|
||||||
end
|
end
|
||||||
return ret
|
return ret
|
||||||
elseif action.type == 'delete' then
|
elseif action.type == "delete" then
|
||||||
return string.format('DELETE %s', action.url)
|
return string.format("DELETE %s", action.url)
|
||||||
elseif action.type == 'move' or action.type == 'copy' then
|
elseif action.type == "move" or action.type == "copy" then
|
||||||
local src = action.src_url
|
local src = action.src_url
|
||||||
local dest = action.dest_url
|
local dest = action.dest_url
|
||||||
if config.get_adapter_by_scheme(src) == M then
|
if config.get_adapter_by_scheme(src) == M then
|
||||||
|
|
@ -284,30 +279,30 @@ M.render_action = function(action)
|
||||||
assert(path)
|
assert(path)
|
||||||
src = files.to_short_os_path(path, action.entry_type)
|
src = files.to_short_os_path(path, action.entry_type)
|
||||||
end
|
end
|
||||||
return string.format(' %s %s -> %s', action.type:upper(), src, dest)
|
return string.format(" %s %s -> %s", action.type:upper(), src, dest)
|
||||||
else
|
else
|
||||||
error(string.format("Bad action type: '%s'", action.type))
|
error(string.format("Bad action type: '%s'", action.type))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param action canola.Action
|
---@param action oil.Action
|
||||||
---@param cb fun(err: nil|string)
|
---@param cb fun(err: nil|string)
|
||||||
M.perform_action = function(action, cb)
|
M.perform_action = function(action, cb)
|
||||||
if action.type == 'create' then
|
if action.type == "create" then
|
||||||
local res = M.parse_url(action.url)
|
local res = M.parse_url(action.url)
|
||||||
local conn = get_connection(action.url)
|
local conn = get_connection(action.url)
|
||||||
if action.entry_type == 'directory' then
|
if action.entry_type == "directory" then
|
||||||
conn:mkdir(res.path, cb)
|
conn:mkdir(res.path, cb)
|
||||||
elseif action.entry_type == 'link' and action.link then
|
elseif action.entry_type == "link" and action.link then
|
||||||
conn:mklink(res.path, action.link, cb)
|
conn:mklink(res.path, action.link, cb)
|
||||||
else
|
else
|
||||||
conn:touch(res.path, cb)
|
conn:touch(res.path, cb)
|
||||||
end
|
end
|
||||||
elseif action.type == 'delete' then
|
elseif action.type == "delete" then
|
||||||
local res = M.parse_url(action.url)
|
local res = M.parse_url(action.url)
|
||||||
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 = assert(config.get_adapter_by_scheme(action.src_url))
|
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
||||||
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
||||||
if src_adapter == M and dest_adapter == M then
|
if src_adapter == M and dest_adapter == M then
|
||||||
|
|
@ -316,7 +311,7 @@ M.perform_action = function(action, cb)
|
||||||
local src_conn = get_connection(action.src_url)
|
local src_conn = get_connection(action.src_url)
|
||||||
local dest_conn = get_connection(action.dest_url)
|
local dest_conn = get_connection(action.dest_url)
|
||||||
if src_conn ~= dest_conn then
|
if src_conn ~= dest_conn then
|
||||||
scp({ '-r', url_to_scp(src_res), url_to_scp(dest_res) }, function(err)
|
scp({ "-r", url_to_scp(src_res), url_to_scp(dest_res) }, function(err)
|
||||||
if err then
|
if err then
|
||||||
return cb(err)
|
return cb(err)
|
||||||
end
|
end
|
||||||
|
|
@ -326,16 +321,16 @@ M.perform_action = function(action, cb)
|
||||||
src_conn:mv(src_res.path, dest_res.path, cb)
|
src_conn:mv(src_res.path, dest_res.path, cb)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
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 = assert(config.get_adapter_by_scheme(action.src_url))
|
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
||||||
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
||||||
if src_adapter == M and dest_adapter == M then
|
if src_adapter == M and dest_adapter == M then
|
||||||
local src_res = M.parse_url(action.src_url)
|
local src_res = M.parse_url(action.src_url)
|
||||||
local dest_res = M.parse_url(action.dest_url)
|
local dest_res = M.parse_url(action.dest_url)
|
||||||
if not url_hosts_equal(src_res, dest_res) then
|
if not url_hosts_equal(src_res, dest_res) then
|
||||||
scp({ '-r', url_to_scp(src_res), url_to_scp(dest_res) }, cb)
|
scp({ "-r", url_to_scp(src_res), url_to_scp(dest_res) }, cb)
|
||||||
else
|
else
|
||||||
local src_conn = get_connection(action.src_url)
|
local src_conn = get_connection(action.src_url)
|
||||||
src_conn:cp(src_res.path, dest_res.path, cb)
|
src_conn:cp(src_res.path, dest_res.path, cb)
|
||||||
|
|
@ -354,14 +349,14 @@ M.perform_action = function(action, cb)
|
||||||
src_arg = fs.posix_to_os_path(path)
|
src_arg = fs.posix_to_os_path(path)
|
||||||
dest_arg = url_to_scp(M.parse_url(action.dest_url))
|
dest_arg = url_to_scp(M.parse_url(action.dest_url))
|
||||||
end
|
end
|
||||||
scp({ '-r', src_arg, dest_arg }, cb)
|
scp({ "-r", src_arg, dest_arg }, cb)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
cb(string.format('Bad action type: %s', action.type))
|
cb(string.format("Bad action type: %s", action.type))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
M.supported_cross_adapter_actions = { files = 'copy' }
|
M.supported_cross_adapter_actions = { files = "copy" }
|
||||||
|
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
M.read_file = function(bufnr)
|
M.read_file = function(bufnr)
|
||||||
|
|
@ -370,11 +365,11 @@ M.read_file = function(bufnr)
|
||||||
local url = M.parse_url(bufname)
|
local url = M.parse_url(bufname)
|
||||||
local scp_url = url_to_scp(url)
|
local scp_url = url_to_scp(url)
|
||||||
local basename = pathutil.basename(bufname)
|
local basename = pathutil.basename(bufname)
|
||||||
local cache_dir = vim.fn.stdpath('cache')
|
local cache_dir = vim.fn.stdpath("cache")
|
||||||
assert(type(cache_dir) == 'string')
|
assert(type(cache_dir) == "string")
|
||||||
local tmpdir = fs.join(cache_dir, 'canola')
|
local tmpdir = fs.join(cache_dir, "oil")
|
||||||
fs.mkdirp(tmpdir)
|
fs.mkdirp(tmpdir)
|
||||||
local fd, tmpfile = vim.loop.fs_mkstemp(fs.join(tmpdir, 'ssh_XXXXXX'))
|
local fd, tmpfile = vim.loop.fs_mkstemp(fs.join(tmpdir, "ssh_XXXXXX"))
|
||||||
if fd then
|
if fd then
|
||||||
vim.loop.fs_close(fd)
|
vim.loop.fs_close(fd)
|
||||||
end
|
end
|
||||||
|
|
@ -383,9 +378,9 @@ M.read_file = function(bufnr)
|
||||||
scp({ scp_url, tmpfile }, function(err)
|
scp({ scp_url, tmpfile }, function(err)
|
||||||
loading.set_loading(bufnr, false)
|
loading.set_loading(bufnr, false)
|
||||||
vim.bo[bufnr].modifiable = true
|
vim.bo[bufnr].modifiable = true
|
||||||
vim.cmd.doautocmd({ args = { 'BufReadPre', bufname }, mods = { silent = true } })
|
vim.cmd.doautocmd({ args = { "BufReadPre", bufname }, mods = { silent = true } })
|
||||||
if err then
|
if err then
|
||||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, vim.split(err, '\n'))
|
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, vim.split(err, "\n"))
|
||||||
else
|
else
|
||||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, {})
|
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, {})
|
||||||
vim.api.nvim_buf_call(bufnr, function()
|
vim.api.nvim_buf_call(bufnr, function()
|
||||||
|
|
@ -399,9 +394,9 @@ M.read_file = function(bufnr)
|
||||||
if filetype then
|
if filetype then
|
||||||
vim.bo[bufnr].filetype = filetype
|
vim.bo[bufnr].filetype = filetype
|
||||||
end
|
end
|
||||||
vim.cmd.doautocmd({ args = { 'BufReadPost', bufname }, mods = { silent = true } })
|
vim.cmd.doautocmd({ args = { "BufReadPost", bufname }, mods = { silent = true } })
|
||||||
vim.api.nvim_buf_delete(tmp_bufnr, { force = true })
|
vim.api.nvim_buf_delete(tmp_bufnr, { force = true })
|
||||||
vim.keymap.set('n', 'gf', M.goto_file, { buffer = bufnr })
|
vim.keymap.set("n", "gf", M.goto_file, { buffer = bufnr })
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -410,14 +405,14 @@ M.write_file = function(bufnr)
|
||||||
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
||||||
local url = M.parse_url(bufname)
|
local url = M.parse_url(bufname)
|
||||||
local scp_url = url_to_scp(url)
|
local scp_url = url_to_scp(url)
|
||||||
local cache_dir = vim.fn.stdpath('cache')
|
local cache_dir = vim.fn.stdpath("cache")
|
||||||
assert(type(cache_dir) == 'string')
|
assert(type(cache_dir) == "string")
|
||||||
local tmpdir = fs.join(cache_dir, 'canola')
|
local tmpdir = fs.join(cache_dir, "oil")
|
||||||
local fd, tmpfile = vim.loop.fs_mkstemp(fs.join(tmpdir, 'ssh_XXXXXXXX'))
|
local fd, tmpfile = vim.loop.fs_mkstemp(fs.join(tmpdir, "ssh_XXXXXXXX"))
|
||||||
if fd then
|
if fd then
|
||||||
vim.loop.fs_close(fd)
|
vim.loop.fs_close(fd)
|
||||||
end
|
end
|
||||||
vim.cmd.doautocmd({ args = { 'BufWritePre', bufname }, mods = { silent = true } })
|
vim.cmd.doautocmd({ args = { "BufWritePre", bufname }, mods = { silent = true } })
|
||||||
vim.bo[bufnr].modifiable = false
|
vim.bo[bufnr].modifiable = false
|
||||||
vim.cmd.write({ args = { tmpfile }, bang = true, mods = { silent = true, noautocmd = true } })
|
vim.cmd.write({ args = { tmpfile }, bang = true, mods = { silent = true, noautocmd = true } })
|
||||||
local tmp_bufnr = vim.fn.bufadd(tmpfile)
|
local tmp_bufnr = vim.fn.bufadd(tmpfile)
|
||||||
|
|
@ -425,10 +420,10 @@ M.write_file = function(bufnr)
|
||||||
scp({ tmpfile, scp_url }, function(err)
|
scp({ tmpfile, scp_url }, function(err)
|
||||||
vim.bo[bufnr].modifiable = true
|
vim.bo[bufnr].modifiable = true
|
||||||
if err then
|
if err then
|
||||||
vim.notify(string.format('Error writing file: %s', err), vim.log.levels.ERROR)
|
vim.notify(string.format("Error writing file: %s", err), vim.log.levels.ERROR)
|
||||||
else
|
else
|
||||||
vim.bo[bufnr].modified = false
|
vim.bo[bufnr].modified = false
|
||||||
vim.cmd.doautocmd({ args = { 'BufWritePost', bufname }, mods = { silent = true } })
|
vim.cmd.doautocmd({ args = { "BufWritePost", bufname }, mods = { silent = true } })
|
||||||
end
|
end
|
||||||
vim.loop.fs_unlink(tmpfile)
|
vim.loop.fs_unlink(tmpfile)
|
||||||
vim.api.nvim_buf_delete(tmp_bufnr, { force = true })
|
vim.api.nvim_buf_delete(tmp_bufnr, { force = true })
|
||||||
|
|
@ -437,7 +432,7 @@ end
|
||||||
|
|
||||||
M.goto_file = function()
|
M.goto_file = function()
|
||||||
local url = M.parse_url(vim.api.nvim_buf_get_name(0))
|
local url = M.parse_url(vim.api.nvim_buf_get_name(0))
|
||||||
local fname = vim.fn.expand('<cfile>')
|
local fname = vim.fn.expand("<cfile>")
|
||||||
local fullpath = fname
|
local fullpath = fname
|
||||||
if not fs.is_absolute(fname) then
|
if not fs.is_absolute(fname) then
|
||||||
local pardir = vim.fs.dirname(url.path)
|
local pardir = vim.fs.dirname(url.path)
|
||||||
|
|
@ -446,7 +441,7 @@ M.goto_file = function()
|
||||||
url.path = vim.fs.dirname(fullpath)
|
url.path = vim.fs.dirname(fullpath)
|
||||||
local parurl = url_to_str(url)
|
local parurl = url_to_str(url)
|
||||||
|
|
||||||
---@cast M canola.Adapter
|
---@cast M oil.Adapter
|
||||||
util.adapter_list_all(M, parurl, {}, function(err, entries)
|
util.adapter_list_all(M, parurl, {}, function(err, entries)
|
||||||
if err then
|
if err then
|
||||||
vim.notify(string.format("Error finding file '%s': %s", fname, err), vim.log.levels.ERROR)
|
vim.notify(string.format("Error finding file '%s': %s", fname, err), vim.log.levels.ERROR)
|
||||||
|
|
@ -464,7 +459,7 @@ M.goto_file = function()
|
||||||
vim.cmd.edit({ args = { url_to_str(url) } })
|
vim.cmd.edit({ args = { url_to_str(url) } })
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
for suffix in vim.gsplit(vim.o.suffixesadd, ',', { plain = true, trimempty = true }) do
|
for suffix in vim.gsplit(vim.o.suffixesadd, ",", { plain = true, trimempty = true }) do
|
||||||
local suffixname = basename .. suffix
|
local suffixname = basename .. suffix
|
||||||
if name_map[suffixname] then
|
if name_map[suffixname] then
|
||||||
url.path = fullpath .. suffix
|
url.path = fullpath .. suffix
|
||||||
|
|
@ -1,22 +1,22 @@
|
||||||
local config = require('canola.config')
|
local config = require("oil.config")
|
||||||
local layout = require('canola.layout')
|
local layout = require("oil.layout")
|
||||||
local util = require('canola.util')
|
local util = require("oil.util")
|
||||||
|
|
||||||
---@class (exact) canola.sshCommand
|
---@class (exact) oil.sshCommand
|
||||||
---@field cmd string|string[]
|
---@field cmd string|string[]
|
||||||
---@field cb fun(err?: string, output?: string[])
|
---@field cb fun(err?: string, output?: string[])
|
||||||
---@field running? boolean
|
---@field running? boolean
|
||||||
|
|
||||||
---@class (exact) canola.sshConnection
|
---@class (exact) oil.sshConnection
|
||||||
---@field new fun(url: canola.sshUrl): canola.sshConnection
|
---@field new fun(url: oil.sshUrl): oil.sshConnection
|
||||||
---@field create_ssh_command fun(url: canola.sshUrl): string[]
|
---@field create_ssh_command fun(url: oil.sshUrl): string[]
|
||||||
---@field meta {user?: string, groups?: string[]}
|
---@field meta {user?: string, groups?: string[]}
|
||||||
---@field connection_error nil|string
|
---@field connection_error nil|string
|
||||||
---@field connected boolean
|
---@field connected boolean
|
||||||
---@field private term_bufnr integer
|
---@field private term_bufnr integer
|
||||||
---@field private jid integer
|
---@field private jid integer
|
||||||
---@field private term_winid nil|integer
|
---@field private term_winid nil|integer
|
||||||
---@field private commands canola.sshCommand[]
|
---@field private commands oil.sshCommand[]
|
||||||
---@field private _stdout string[]
|
---@field private _stdout string[]
|
||||||
local SSHConnection = {}
|
local SSHConnection = {}
|
||||||
|
|
||||||
|
|
@ -24,12 +24,12 @@ local function output_extend(agg, output)
|
||||||
local start = #agg
|
local start = #agg
|
||||||
if vim.tbl_isempty(agg) then
|
if vim.tbl_isempty(agg) then
|
||||||
for _, line in ipairs(output) do
|
for _, line in ipairs(output) do
|
||||||
line = line:gsub('\r', '')
|
line = line:gsub("\r", "")
|
||||||
table.insert(agg, line)
|
table.insert(agg, line)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
for i, v in ipairs(output) do
|
for i, v in ipairs(output) do
|
||||||
v = v:gsub('\r', '')
|
v = v:gsub("\r", "")
|
||||||
if i == 1 then
|
if i == 1 then
|
||||||
agg[#agg] = agg[#agg] .. v
|
agg[#agg] = agg[#agg] .. v
|
||||||
else
|
else
|
||||||
|
|
@ -53,7 +53,7 @@ local function get_last_lines(bufnr, num_lines)
|
||||||
vim.api.nvim_buf_get_lines(bufnr, end_line - need_lines, end_line, false),
|
vim.api.nvim_buf_get_lines(bufnr, end_line - need_lines, end_line, false),
|
||||||
lines
|
lines
|
||||||
)
|
)
|
||||||
while not vim.tbl_isempty(lines) and lines[#lines]:match('^%s*$') do
|
while not vim.tbl_isempty(lines) and lines[#lines]:match("^%s*$") do
|
||||||
table.remove(lines)
|
table.remove(lines)
|
||||||
end
|
end
|
||||||
end_line = end_line - need_lines
|
end_line = end_line - need_lines
|
||||||
|
|
@ -61,31 +61,31 @@ local function get_last_lines(bufnr, num_lines)
|
||||||
return lines
|
return lines
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param url canola.sshUrl
|
---@param url oil.sshUrl
|
||||||
---@return string[]
|
---@return string[]
|
||||||
function SSHConnection.create_ssh_command(url)
|
function SSHConnection.create_ssh_command(url)
|
||||||
local host = url.host
|
local host = url.host
|
||||||
if url.user then
|
if url.user then
|
||||||
host = url.user .. '@' .. host
|
host = url.user .. "@" .. host
|
||||||
end
|
end
|
||||||
local command = {
|
local command = {
|
||||||
'ssh',
|
"ssh",
|
||||||
host,
|
host,
|
||||||
}
|
}
|
||||||
if url.port then
|
if url.port then
|
||||||
table.insert(command, '-p')
|
table.insert(command, "-p")
|
||||||
table.insert(command, url.port)
|
table.insert(command, url.port)
|
||||||
end
|
end
|
||||||
return command
|
return command
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param url canola.sshUrl
|
---@param url oil.sshUrl
|
||||||
---@return canola.sshConnection
|
---@return oil.sshConnection
|
||||||
function SSHConnection.new(url)
|
function SSHConnection.new(url)
|
||||||
local command = SSHConnection.create_ssh_command(url)
|
local command = SSHConnection.create_ssh_command(url)
|
||||||
vim.list_extend(command, {
|
vim.list_extend(command, {
|
||||||
'/bin/sh',
|
"/bin/sh",
|
||||||
'-c',
|
"-c",
|
||||||
-- HACK: For some reason in my testing if I just have "echo READY" it doesn't appear, but if I echo
|
-- HACK: For some reason in my testing if I just have "echo READY" it doesn't appear, but if I echo
|
||||||
-- anything prior to that, it *will* appear. The first line gets swallowed.
|
-- anything prior to that, it *will* appear. The first line gets swallowed.
|
||||||
"echo '_make_newline_'; echo '===READY==='; exec /bin/sh",
|
"echo '_make_newline_'; echo '===READY==='; exec /bin/sh",
|
||||||
|
|
@ -112,7 +112,7 @@ function SSHConnection.new(url)
|
||||||
})
|
})
|
||||||
end)
|
end)
|
||||||
self.term_id = term_id
|
self.term_id = term_id
|
||||||
vim.api.nvim_chan_send(term_id, string.format('ssh %s\r\n', url.host))
|
vim.api.nvim_chan_send(term_id, string.format("ssh %s\r\n", url.host))
|
||||||
util.hack_around_termopen_autocmd(mode)
|
util.hack_around_termopen_autocmd(mode)
|
||||||
|
|
||||||
-- If it takes more than 2 seconds to connect, pop open the terminal
|
-- If it takes more than 2 seconds to connect, pop open the terminal
|
||||||
|
|
@ -125,7 +125,7 @@ function SSHConnection.new(url)
|
||||||
local jid = vim.fn.jobstart(command, {
|
local jid = vim.fn.jobstart(command, {
|
||||||
pty = true, -- This is require for interactivity
|
pty = true, -- This is require for interactivity
|
||||||
on_stdout = function(j, output)
|
on_stdout = function(j, output)
|
||||||
pcall(vim.api.nvim_chan_send, self.term_id, table.concat(output, '\r\n'))
|
pcall(vim.api.nvim_chan_send, self.term_id, table.concat(output, "\r\n"))
|
||||||
---@diagnostic disable-next-line: invisible
|
---@diagnostic disable-next-line: invisible
|
||||||
local new_i_start = output_extend(self._stdout, output)
|
local new_i_start = output_extend(self._stdout, output)
|
||||||
self:_handle_output(new_i_start)
|
self:_handle_output(new_i_start)
|
||||||
|
|
@ -134,15 +134,15 @@ function SSHConnection.new(url)
|
||||||
pcall(
|
pcall(
|
||||||
vim.api.nvim_chan_send,
|
vim.api.nvim_chan_send,
|
||||||
self.term_id,
|
self.term_id,
|
||||||
string.format('\r\n[Process exited %d]\r\n', code)
|
string.format("\r\n[Process exited %d]\r\n", code)
|
||||||
)
|
)
|
||||||
-- Defer to allow the deferred terminal output handling to kick in first
|
-- Defer to allow the deferred terminal output handling to kick in first
|
||||||
vim.defer_fn(function()
|
vim.defer_fn(function()
|
||||||
if code == 0 then
|
if code == 0 then
|
||||||
self:_set_connection_error('SSH connection terminated gracefully')
|
self:_set_connection_error("SSH connection terminated gracefully")
|
||||||
else
|
else
|
||||||
self:_set_connection_error(
|
self:_set_connection_error(
|
||||||
'Unknown SSH error\nTo see more, run :lua require("canola.adapters.ssh").open_terminal()'
|
'Unknown SSH error\nTo see more, run :lua require("oil.adapters.ssh").open_terminal()'
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end, 20)
|
end, 20)
|
||||||
|
|
@ -156,27 +156,27 @@ function SSHConnection.new(url)
|
||||||
else
|
else
|
||||||
self.jid = jid
|
self.jid = jid
|
||||||
end
|
end
|
||||||
self:run('id -u', function(err, lines)
|
self:run("id -u", function(err, lines)
|
||||||
if err then
|
if err then
|
||||||
vim.notify(string.format('Error fetching ssh connection user: %s', err), vim.log.levels.WARN)
|
vim.notify(string.format("Error fetching ssh connection user: %s", err), vim.log.levels.WARN)
|
||||||
else
|
else
|
||||||
assert(lines)
|
assert(lines)
|
||||||
self.meta.user = vim.trim(table.concat(lines, ''))
|
self.meta.user = vim.trim(table.concat(lines, ""))
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
self:run('id -G', function(err, lines)
|
self:run("id -G", function(err, lines)
|
||||||
if err then
|
if err then
|
||||||
vim.notify(
|
vim.notify(
|
||||||
string.format('Error fetching ssh connection user groups: %s', err),
|
string.format("Error fetching ssh connection user groups: %s", err),
|
||||||
vim.log.levels.WARN
|
vim.log.levels.WARN
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
assert(lines)
|
assert(lines)
|
||||||
self.meta.groups = vim.split(table.concat(lines, ''), '%s+', { trimempty = true })
|
self.meta.groups = vim.split(table.concat(lines, ""), "%s+", { trimempty = true })
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
---@cast self canola.sshConnection
|
---@cast self oil.sshConnection
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -197,7 +197,7 @@ function SSHConnection:_handle_output(start_i)
|
||||||
if not self.connected then
|
if not self.connected then
|
||||||
for i = start_i, #self._stdout - 1 do
|
for i = start_i, #self._stdout - 1 do
|
||||||
local line = self._stdout[i]
|
local line = self._stdout[i]
|
||||||
if line == '===READY===' then
|
if line == "===READY===" then
|
||||||
if self.term_winid then
|
if self.term_winid then
|
||||||
if vim.api.nvim_win_is_valid(self.term_winid) then
|
if vim.api.nvim_win_is_valid(self.term_winid) then
|
||||||
vim.api.nvim_win_close(self.term_winid, true)
|
vim.api.nvim_win_close(self.term_winid, true)
|
||||||
|
|
@ -215,7 +215,7 @@ function SSHConnection:_handle_output(start_i)
|
||||||
for i = start_i, #self._stdout - 1 do
|
for i = start_i, #self._stdout - 1 do
|
||||||
---@type string
|
---@type string
|
||||||
local line = self._stdout[i]
|
local line = self._stdout[i]
|
||||||
if line:match('^===BEGIN===%s*$') then
|
if line:match("^===BEGIN===%s*$") then
|
||||||
self._stdout = util.tbl_slice(self._stdout, i + 1)
|
self._stdout = util.tbl_slice(self._stdout, i + 1)
|
||||||
self:_handle_output(1)
|
self:_handle_output(1)
|
||||||
return
|
return
|
||||||
|
|
@ -223,15 +223,15 @@ function SSHConnection:_handle_output(start_i)
|
||||||
-- We can't be as strict with the matching (^$) because since we're using a pty the stdout and
|
-- We can't be as strict with the matching (^$) because since we're using a pty the stdout and
|
||||||
-- stderr can be interleaved. If the command had an error, the stderr may interfere with a
|
-- stderr can be interleaved. If the command had an error, the stderr may interfere with a
|
||||||
-- clean print of the done line.
|
-- clean print of the done line.
|
||||||
local exit_code = line:match('===DONE%((%d+)%)===')
|
local exit_code = line:match("===DONE%((%d+)%)===")
|
||||||
if exit_code then
|
if exit_code then
|
||||||
local output = util.tbl_slice(self._stdout, 1, i - 1)
|
local output = util.tbl_slice(self._stdout, 1, i - 1)
|
||||||
local cb = self.commands[1].cb
|
local cb = self.commands[1].cb
|
||||||
self._stdout = util.tbl_slice(self._stdout, i + 1)
|
self._stdout = util.tbl_slice(self._stdout, i + 1)
|
||||||
if exit_code == '0' then
|
if exit_code == "0" then
|
||||||
cb(nil, output)
|
cb(nil, output)
|
||||||
else
|
else
|
||||||
cb(exit_code .. ': ' .. table.concat(output, '\n'), output)
|
cb(exit_code .. ": " .. table.concat(output, "\n"), output)
|
||||||
end
|
end
|
||||||
table.remove(self.commands, 1)
|
table.remove(self.commands, 1)
|
||||||
self:_handle_output(1)
|
self:_handle_output(1)
|
||||||
|
|
@ -244,17 +244,16 @@ function SSHConnection:_handle_output(start_i)
|
||||||
local function check_last_line()
|
local function check_last_line()
|
||||||
local last_lines = get_last_lines(self.term_bufnr, 1)
|
local last_lines = get_last_lines(self.term_bufnr, 1)
|
||||||
local last_line = last_lines[1]
|
local last_line = last_lines[1]
|
||||||
if last_line:match('^Are you sure you want to continue connecting') then
|
if last_line:match("^Are you sure you want to continue connecting") then
|
||||||
self:open_terminal()
|
self:open_terminal()
|
||||||
-- selene: allow(if_same_then_else)
|
elseif last_line:match("Password:%s*$") then
|
||||||
elseif last_line:match('Password:%s*$') then
|
|
||||||
self:open_terminal()
|
self:open_terminal()
|
||||||
elseif last_line:match(': Permission denied %(.+%)%.') then
|
elseif last_line:match(": Permission denied %(.+%)%.") then
|
||||||
self:_set_connection_error(last_line:match(': (Permission denied %(.+%).)'))
|
self:_set_connection_error(last_line:match(": (Permission denied %(.+%).)"))
|
||||||
elseif last_line:match('^ssh: .*Connection refused%s*$') then
|
elseif last_line:match("^ssh: .*Connection refused%s*$") then
|
||||||
self:_set_connection_error('Connection refused')
|
self:_set_connection_error("Connection refused")
|
||||||
elseif last_line:match('^Connection to .+ closed by remote host.%s*$') then
|
elseif last_line:match("^Connection to .+ closed by remote host.%s*$") then
|
||||||
self:_set_connection_error('Connection closed by remote host')
|
self:_set_connection_error("Connection closed by remote host")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
-- We have to defer this so the terminal buffer has time to update
|
-- We have to defer this so the terminal buffer has time to update
|
||||||
|
|
@ -274,12 +273,12 @@ function SSHConnection:open_terminal()
|
||||||
local row = math.floor((total_height - height) / 2)
|
local row = math.floor((total_height - height) / 2)
|
||||||
local col = math.floor((vim.o.columns - width) / 2)
|
local col = math.floor((vim.o.columns - width) / 2)
|
||||||
self.term_winid = vim.api.nvim_open_win(self.term_bufnr, true, {
|
self.term_winid = vim.api.nvim_open_win(self.term_bufnr, true, {
|
||||||
relative = 'editor',
|
relative = "editor",
|
||||||
width = width,
|
width = width,
|
||||||
height = height,
|
height = height,
|
||||||
row = row,
|
row = row,
|
||||||
col = col,
|
col = col,
|
||||||
style = 'minimal',
|
style = "minimal",
|
||||||
border = config.ssh.border,
|
border = config.ssh.border,
|
||||||
})
|
})
|
||||||
vim.cmd.startinsert()
|
vim.cmd.startinsert()
|
||||||
|
|
@ -1,37 +1,37 @@
|
||||||
local SSHConnection = require('canola.adapters.ssh.connection')
|
local SSHConnection = require("oil.adapters.ssh.connection")
|
||||||
local cache = require('canola.cache')
|
local cache = require("oil.cache")
|
||||||
local constants = require('canola.constants')
|
local constants = require("oil.constants")
|
||||||
local permissions = require('canola.adapters.files.permissions')
|
local permissions = require("oil.adapters.files.permissions")
|
||||||
local util = require('canola.util')
|
local util = require("oil.util")
|
||||||
|
|
||||||
---@class (exact) canola.sshFs
|
---@class (exact) oil.sshFs
|
||||||
---@field new fun(url: canola.sshUrl): canola.sshFs
|
---@field new fun(url: oil.sshUrl): oil.sshFs
|
||||||
---@field conn canola.sshConnection
|
---@field conn oil.sshConnection
|
||||||
local SSHFS = {}
|
local SSHFS = {}
|
||||||
|
|
||||||
local FIELD_TYPE = constants.FIELD_TYPE
|
local FIELD_TYPE = constants.FIELD_TYPE
|
||||||
local FIELD_META = constants.FIELD_META
|
local FIELD_META = constants.FIELD_META
|
||||||
|
|
||||||
local typechar_map = {
|
local typechar_map = {
|
||||||
l = 'link',
|
l = "link",
|
||||||
d = 'directory',
|
d = "directory",
|
||||||
p = 'fifo',
|
p = "fifo",
|
||||||
s = 'socket',
|
s = "socket",
|
||||||
['-'] = 'file',
|
["-"] = "file",
|
||||||
c = 'file', -- character special file
|
c = "file", -- character special file
|
||||||
b = 'file', -- block special file
|
b = "file", -- block special file
|
||||||
}
|
}
|
||||||
---@param line string
|
---@param line string
|
||||||
---@return string Name of entry
|
---@return string Name of entry
|
||||||
---@return canola.EntryType
|
---@return oil.EntryType
|
||||||
---@return table Metadata for entry
|
---@return table Metadata for entry
|
||||||
local function parse_ls_line(line)
|
local function parse_ls_line(line)
|
||||||
local typechar, perms, refcount, user, group, rem =
|
local typechar, perms, refcount, user, group, rem =
|
||||||
line:match('^(.)(%S+)%s+(%d+)%s+(%d+)%s+(%d+)%s+(.*)$')
|
line:match("^(.)(%S+)%s+(%d+)%s+(%d+)%s+(%d+)%s+(.*)$")
|
||||||
if not typechar then
|
if not typechar then
|
||||||
error(string.format("Could not parse '%s'", line))
|
error(string.format("Could not parse '%s'", line))
|
||||||
end
|
end
|
||||||
local type = typechar_map[typechar] or 'file'
|
local type = typechar_map[typechar] or "file"
|
||||||
|
|
||||||
local meta = {
|
local meta = {
|
||||||
user = user,
|
user = user,
|
||||||
|
|
@ -40,26 +40,26 @@ local function parse_ls_line(line)
|
||||||
refcount = tonumber(refcount),
|
refcount = tonumber(refcount),
|
||||||
}
|
}
|
||||||
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
|
if name == nil then
|
||||||
major, minor, date, name =
|
major, minor, date, name =
|
||||||
rem:match('^(%d+)%s*,%s*(%d+)%s+(%d+%-%d+%-%d+%s+%d%d:?%d%d)%s+(.*)')
|
rem:match("^(%d+)%s*,%s*(%d+)%s+(%d+%-%d+%-%d+%s+%d%d:?%d%d)%s+(.*)")
|
||||||
end
|
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
|
if name == nil then
|
||||||
size, date, name = rem:match('^(%d+)%s+(%d+%-%d+%-%d+%s+%d%d:?%d%d)%s+(.*)')
|
size, date, name = rem:match("^(%d+)%s+(%d+%-%d+%-%d+%s+%d%d:?%d%d)%s+(.*)")
|
||||||
end
|
end
|
||||||
meta.size = tonumber(size)
|
meta.size = tonumber(size)
|
||||||
end
|
end
|
||||||
meta.iso_modified_date = date
|
meta.iso_modified_date = date
|
||||||
if type == 'link' then
|
if type == "link" then
|
||||||
local link
|
local link
|
||||||
name, link = unpack(vim.split(name, ' -> ', { plain = true }))
|
name, link = unpack(vim.split(name, " -> ", { plain = true }))
|
||||||
if vim.endswith(link, '/') then
|
if vim.endswith(link, "/") then
|
||||||
link = link:sub(1, #link - 1)
|
link = link:sub(1, #link - 1)
|
||||||
end
|
end
|
||||||
meta.link = link
|
meta.link = link
|
||||||
|
|
@ -74,10 +74,10 @@ local function shellescape(str)
|
||||||
return "'" .. str:gsub("'", "'\\''") .. "'"
|
return "'" .. str:gsub("'", "'\\''") .. "'"
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param url canola.sshUrl
|
---@param url oil.sshUrl
|
||||||
---@return canola.sshFs
|
---@return oil.sshFs
|
||||||
function SSHFS.new(url)
|
function SSHFS.new(url)
|
||||||
---@type canola.sshFs
|
---@type oil.sshFs
|
||||||
return setmetatable({
|
return setmetatable({
|
||||||
conn = SSHConnection.new(url),
|
conn = SSHConnection.new(url),
|
||||||
}, {
|
}, {
|
||||||
|
|
@ -94,7 +94,7 @@ end
|
||||||
---@param callback fun(err: nil|string)
|
---@param callback fun(err: nil|string)
|
||||||
function SSHFS:chmod(value, path, callback)
|
function SSHFS:chmod(value, path, callback)
|
||||||
local octal = permissions.mode_to_octal_str(value)
|
local octal = permissions.mode_to_octal_str(value)
|
||||||
self.conn:run(string.format('chmod %s %s', octal, shellescape(path)), callback)
|
self.conn:run(string.format("chmod %s %s", octal, shellescape(path)), callback)
|
||||||
end
|
end
|
||||||
|
|
||||||
function SSHFS:open_terminal()
|
function SSHFS:open_terminal()
|
||||||
|
|
@ -114,24 +114,24 @@ function SSHFS:realpath(path, callback)
|
||||||
return callback(err)
|
return callback(err)
|
||||||
end
|
end
|
||||||
assert(lines)
|
assert(lines)
|
||||||
local abspath = table.concat(lines, '')
|
local abspath = table.concat(lines, "")
|
||||||
-- If the path was "." then the abspath might be /path/to/., so we need to trim that final '.'
|
-- If the path was "." then the abspath might be /path/to/., so we need to trim that final '.'
|
||||||
if vim.endswith(abspath, '.') then
|
if vim.endswith(abspath, ".") then
|
||||||
abspath = abspath:sub(1, #abspath - 1)
|
abspath = abspath:sub(1, #abspath - 1)
|
||||||
end
|
end
|
||||||
self.conn:run(
|
self.conn:run(
|
||||||
string.format('LC_ALL=C ls -land --color=never %s', shellescape(abspath)),
|
string.format("LC_ALL=C ls -land --color=never %s", shellescape(abspath)),
|
||||||
function(ls_err, ls_lines)
|
function(ls_err, ls_lines)
|
||||||
local type
|
local type
|
||||||
if ls_err then
|
if ls_err then
|
||||||
-- If the file doesn't exist, treat it like a not-yet-existing directory
|
-- If the file doesn't exist, treat it like a not-yet-existing directory
|
||||||
type = 'directory'
|
type = "directory"
|
||||||
else
|
else
|
||||||
assert(ls_lines)
|
assert(ls_lines)
|
||||||
local _
|
local _
|
||||||
_, type = parse_ls_line(ls_lines[1])
|
_, type = parse_ls_line(ls_lines[1])
|
||||||
end
|
end
|
||||||
if type == 'directory' then
|
if type == "directory" then
|
||||||
abspath = util.addslash(abspath)
|
abspath = util.addslash(abspath)
|
||||||
end
|
end
|
||||||
callback(nil, abspath)
|
callback(nil, abspath)
|
||||||
|
|
@ -144,15 +144,15 @@ local dir_meta = {}
|
||||||
|
|
||||||
---@param url string
|
---@param url string
|
||||||
---@param path string
|
---@param path string
|
||||||
---@param callback fun(err?: string, entries?: canola.InternalEntry[], fetch_more?: fun())
|
---@param callback fun(err?: string, entries?: oil.InternalEntry[], fetch_more?: fun())
|
||||||
function SSHFS:list_dir(url, path, callback)
|
function SSHFS:list_dir(url, path, callback)
|
||||||
local path_postfix = ''
|
local path_postfix = ""
|
||||||
if path ~= '' then
|
if path ~= "" then
|
||||||
path_postfix = string.format(' %s', shellescape(path))
|
path_postfix = string.format(" %s", shellescape(path))
|
||||||
end
|
end
|
||||||
self.conn:run('LC_ALL=C ls -lan --color=never' .. path_postfix, function(err, lines)
|
self.conn:run("LC_ALL=C ls -lan --color=never" .. path_postfix, function(err, lines)
|
||||||
if err then
|
if err then
|
||||||
if err:match('No such file or directory%s*$') then
|
if err:match("No such file or directory%s*$") then
|
||||||
-- If the directory doesn't exist, treat the list as a success. We will be able to traverse
|
-- If the directory doesn't exist, treat the list as a success. We will be able to traverse
|
||||||
-- and edit a not-yet-existing directory.
|
-- and edit a not-yet-existing directory.
|
||||||
return callback()
|
return callback()
|
||||||
|
|
@ -165,12 +165,12 @@ function SSHFS:list_dir(url, path, callback)
|
||||||
local entries = {}
|
local entries = {}
|
||||||
local cache_entries = {}
|
local cache_entries = {}
|
||||||
for _, line in ipairs(lines) do
|
for _, line in ipairs(lines) do
|
||||||
if line ~= '' and not line:match('^total') then
|
if line ~= "" and not line:match("^total") then
|
||||||
local name, type, meta = parse_ls_line(line)
|
local name, type, meta = parse_ls_line(line)
|
||||||
if name == '.' then
|
if name == "." then
|
||||||
dir_meta[url] = meta
|
dir_meta[url] = meta
|
||||||
elseif name ~= '..' then
|
elseif name ~= ".." then
|
||||||
if type == 'link' then
|
if type == "link" then
|
||||||
any_links = true
|
any_links = true
|
||||||
end
|
end
|
||||||
local cache_entry = cache.create_entry(url, name, type)
|
local cache_entry = cache.create_entry(url, name, type)
|
||||||
|
|
@ -184,19 +184,19 @@ function SSHFS:list_dir(url, path, callback)
|
||||||
-- If there were any soft links, then we need to run another ls command with -L so that we can
|
-- If there were any soft links, then we need to run another ls command with -L so that we can
|
||||||
-- resolve the type of the link target
|
-- resolve the type of the link target
|
||||||
self.conn:run(
|
self.conn:run(
|
||||||
'LC_ALL=C ls -naLl --color=never' .. path_postfix .. ' 2> /dev/null',
|
"LC_ALL=C ls -naLl --color=never" .. path_postfix .. " 2> /dev/null",
|
||||||
function(link_err, link_lines)
|
function(link_err, link_lines)
|
||||||
-- Ignore exit code 1. That just means one of the links could not be resolved.
|
-- Ignore exit code 1. That just means one of the links could not be resolved.
|
||||||
if link_err and not link_err:match('^1:') then
|
if link_err and not link_err:match("^1:") then
|
||||||
return callback(link_err)
|
return callback(link_err)
|
||||||
end
|
end
|
||||||
assert(link_lines)
|
assert(link_lines)
|
||||||
for _, line in ipairs(link_lines) do
|
for _, line in ipairs(link_lines) do
|
||||||
if line ~= '' and not line:match('^total') then
|
if line ~= "" and not line:match("^total") then
|
||||||
local ok, name, type, meta = pcall(parse_ls_line, line)
|
local ok, name, type, meta = pcall(parse_ls_line, line)
|
||||||
if ok and name ~= '.' and name ~= '..' then
|
if ok and name ~= "." and name ~= ".." then
|
||||||
local cache_entry = entries[name]
|
local cache_entry = entries[name]
|
||||||
if cache_entry[FIELD_TYPE] == 'link' then
|
if cache_entry[FIELD_TYPE] == "link" then
|
||||||
cache_entry[FIELD_META].link_stat = {
|
cache_entry[FIELD_META].link_stat = {
|
||||||
type = type,
|
type = type,
|
||||||
size = meta.size,
|
size = meta.size,
|
||||||
|
|
@ -217,40 +217,40 @@ end
|
||||||
---@param path string
|
---@param path string
|
||||||
---@param callback fun(err: nil|string)
|
---@param callback fun(err: nil|string)
|
||||||
function SSHFS:mkdir(path, callback)
|
function SSHFS:mkdir(path, callback)
|
||||||
self.conn:run(string.format('mkdir -p %s', shellescape(path)), callback)
|
self.conn:run(string.format("mkdir -p %s", shellescape(path)), callback)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param path string
|
---@param path string
|
||||||
---@param callback fun(err: nil|string)
|
---@param callback fun(err: nil|string)
|
||||||
function SSHFS:touch(path, callback)
|
function SSHFS:touch(path, callback)
|
||||||
self.conn:run(string.format('touch %s', shellescape(path)), callback)
|
self.conn:run(string.format("touch %s", shellescape(path)), callback)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param path string
|
---@param path string
|
||||||
---@param link string
|
---@param link string
|
||||||
---@param callback fun(err: nil|string)
|
---@param callback fun(err: nil|string)
|
||||||
function SSHFS:mklink(path, link, callback)
|
function SSHFS:mklink(path, link, callback)
|
||||||
self.conn:run(string.format('ln -s %s %s', shellescape(link), shellescape(path)), callback)
|
self.conn:run(string.format("ln -s %s %s", shellescape(link), shellescape(path)), callback)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param path string
|
---@param path string
|
||||||
---@param callback fun(err: nil|string)
|
---@param callback fun(err: nil|string)
|
||||||
function SSHFS:rm(path, callback)
|
function SSHFS:rm(path, callback)
|
||||||
self.conn:run(string.format('rm -rf %s', shellescape(path)), callback)
|
self.conn:run(string.format("rm -rf %s", shellescape(path)), callback)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param src string
|
---@param src string
|
||||||
---@param dest string
|
---@param dest string
|
||||||
---@param callback fun(err: nil|string)
|
---@param callback fun(err: nil|string)
|
||||||
function SSHFS:mv(src, dest, callback)
|
function SSHFS:mv(src, dest, callback)
|
||||||
self.conn:run(string.format('mv %s %s', shellescape(src), shellescape(dest)), callback)
|
self.conn:run(string.format("mv %s %s", shellescape(src), shellescape(dest)), callback)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param src string
|
---@param src string
|
||||||
---@param dest string
|
---@param dest string
|
||||||
---@param callback fun(err: nil|string)
|
---@param callback fun(err: nil|string)
|
||||||
function SSHFS:cp(src, dest, callback)
|
function SSHFS:cp(src, dest, callback)
|
||||||
self.conn:run(string.format('cp -r %s %s', shellescape(src), shellescape(dest)), callback)
|
self.conn:run(string.format("cp -r %s %s", shellescape(src), shellescape(dest)), callback)
|
||||||
end
|
end
|
||||||
|
|
||||||
function SSHFS:get_dir_meta(url)
|
function SSHFS:get_dir_meta(url)
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
local cache = require('canola.cache')
|
local cache = require("oil.cache")
|
||||||
local util = require('canola.util')
|
local util = require("oil.util")
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
---@param url string
|
---@param url string
|
||||||
|
|
@ -12,7 +12,7 @@ local dir_listing = {}
|
||||||
|
|
||||||
---@param url string
|
---@param url string
|
||||||
---@param column_defs string[]
|
---@param column_defs string[]
|
||||||
---@param cb fun(err?: string, entries?: canola.InternalEntry[], fetch_more?: fun())
|
---@param cb fun(err?: string, entries?: oil.InternalEntry[], fetch_more?: fun())
|
||||||
M.list = function(url, column_defs, cb)
|
M.list = function(url, column_defs, cb)
|
||||||
local _, path = util.parse_url(url)
|
local _, path = util.parse_url(url)
|
||||||
local entries = dir_listing[path] or {}
|
local entries = dir_listing[path] or {}
|
||||||
|
|
@ -29,32 +29,32 @@ M.test_clear = function()
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param path string
|
---@param path string
|
||||||
---@param entry_type canola.EntryType
|
---@param entry_type oil.EntryType
|
||||||
---@return canola.InternalEntry
|
---@return oil.InternalEntry
|
||||||
M.test_set = function(path, entry_type)
|
M.test_set = function(path, entry_type)
|
||||||
if path == '/' then
|
if path == "/" then
|
||||||
return {}
|
return {}
|
||||||
end
|
end
|
||||||
local parent = vim.fn.fnamemodify(path, ':h')
|
local parent = vim.fn.fnamemodify(path, ":h")
|
||||||
if parent ~= path then
|
if parent ~= path then
|
||||||
M.test_set(parent, 'directory')
|
M.test_set(parent, "directory")
|
||||||
end
|
end
|
||||||
parent = util.addslash(parent)
|
parent = util.addslash(parent)
|
||||||
if not dir_listing[parent] then
|
if not dir_listing[parent] then
|
||||||
dir_listing[parent] = {}
|
dir_listing[parent] = {}
|
||||||
end
|
end
|
||||||
local name = vim.fn.fnamemodify(path, ':t')
|
local name = vim.fn.fnamemodify(path, ":t")
|
||||||
local entry = {
|
local entry = {
|
||||||
name = name,
|
name = name,
|
||||||
entry_type = entry_type,
|
entry_type = entry_type,
|
||||||
}
|
}
|
||||||
table.insert(dir_listing[parent], entry)
|
table.insert(dir_listing[parent], entry)
|
||||||
local parent_url = 'canola-test://' .. parent
|
local parent_url = "oil-test://" .. parent
|
||||||
return cache.create_and_store_entry(parent_url, entry.name, entry.entry_type)
|
return cache.create_and_store_entry(parent_url, entry.name, entry.entry_type)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param name string
|
---@param name string
|
||||||
---@return nil|canola.ColumnDefinition
|
---@return nil|oil.ColumnDefinition
|
||||||
M.get_column = function(name)
|
M.get_column = function(name)
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
@ -65,19 +65,19 @@ M.is_modifiable = function(bufnr)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param action canola.Action
|
---@param action oil.Action
|
||||||
---@return string
|
---@return string
|
||||||
M.render_action = function(action)
|
M.render_action = function(action)
|
||||||
if action.type == 'create' or action.type == 'delete' then
|
if action.type == "create" or action.type == "delete" then
|
||||||
return string.format('%s %s', action.type:upper(), action.url)
|
return string.format("%s %s", action.type:upper(), action.url)
|
||||||
elseif action.type == 'move' or action.type == 'copy' then
|
elseif action.type == "move" or action.type == "copy" then
|
||||||
return string.format(' %s %s -> %s', action.type:upper(), action.src_url, action.dest_url)
|
return string.format(" %s %s -> %s", action.type:upper(), action.src_url, action.dest_url)
|
||||||
else
|
else
|
||||||
error('Bad action type')
|
error("Bad action type")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param action canola.Action
|
---@param action oil.Action
|
||||||
---@param cb fun(err: nil|string)
|
---@param cb fun(err: nil|string)
|
||||||
M.perform_action = function(action, cb)
|
M.perform_action = function(action, cb)
|
||||||
cb()
|
cb()
|
||||||
9
lua/oil/adapters/trash.lua
Normal file
9
lua/oil/adapters/trash.lua
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
local fs = require("oil.fs")
|
||||||
|
|
||||||
|
if fs.is_mac then
|
||||||
|
return require("oil.adapters.trash.mac")
|
||||||
|
elseif fs.is_windows then
|
||||||
|
return require("oil.adapters.trash.windows")
|
||||||
|
else
|
||||||
|
return require("oil.adapters.trash.freedesktop")
|
||||||
|
end
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
-- Based on the FreeDesktop.org trash specification
|
-- Based on the FreeDesktop.org trash specification
|
||||||
-- https://specifications.freedesktop.org/trash/1.0/
|
-- https://specifications.freedesktop.org/trash/1.0/
|
||||||
local cache = require('canola.cache')
|
local cache = require("oil.cache")
|
||||||
local config = require('canola.config')
|
local config = require("oil.config")
|
||||||
local constants = require('canola.constants')
|
local constants = require("oil.constants")
|
||||||
local files = require('canola.adapters.files')
|
local files = require("oil.adapters.files")
|
||||||
local fs = require('canola.fs')
|
local fs = require("oil.fs")
|
||||||
local util = require('canola.util')
|
local util = require("oil.util")
|
||||||
|
|
||||||
local uv = vim.uv or vim.loop
|
local uv = vim.uv or vim.loop
|
||||||
local FIELD_META = constants.FIELD_META
|
local FIELD_META = constants.FIELD_META
|
||||||
|
|
@ -14,8 +14,8 @@ local M = {}
|
||||||
|
|
||||||
local function ensure_trash_dir(path)
|
local function ensure_trash_dir(path)
|
||||||
local mode = 448 -- 0700
|
local mode = 448 -- 0700
|
||||||
fs.mkdirp(fs.join(path, 'info'), mode)
|
fs.mkdirp(fs.join(path, "info"), mode)
|
||||||
fs.mkdirp(fs.join(path, 'files'), mode)
|
fs.mkdirp(fs.join(path, "files"), mode)
|
||||||
end
|
end
|
||||||
|
|
||||||
---Gets the location of the home trash dir, creating it if necessary
|
---Gets the location of the home trash dir, creating it if necessary
|
||||||
|
|
@ -23,9 +23,9 @@ end
|
||||||
local function get_home_trash_dir()
|
local function get_home_trash_dir()
|
||||||
local xdg_home = vim.env.XDG_DATA_HOME
|
local xdg_home = vim.env.XDG_DATA_HOME
|
||||||
if not xdg_home then
|
if not xdg_home then
|
||||||
xdg_home = fs.join(assert(uv.os_homedir()), '.local', 'share')
|
xdg_home = fs.join(assert(uv.os_homedir()), ".local", "share")
|
||||||
end
|
end
|
||||||
local trash_dir = fs.join(xdg_home, 'Trash')
|
local trash_dir = fs.join(xdg_home, "Trash")
|
||||||
ensure_trash_dir(trash_dir)
|
ensure_trash_dir(trash_dir)
|
||||||
return trash_dir
|
return trash_dir
|
||||||
end
|
end
|
||||||
|
|
@ -43,13 +43,13 @@ end
|
||||||
local function get_top_trash_dirs(path)
|
local function get_top_trash_dirs(path)
|
||||||
local dirs = {}
|
local dirs = {}
|
||||||
local dev = (uv.fs_lstat(path) or {}).dev
|
local dev = (uv.fs_lstat(path) or {}).dev
|
||||||
local top_trash_dirs = vim.fs.find('.Trash', { upward = true, path = path, limit = math.huge })
|
local top_trash_dirs = vim.fs.find(".Trash", { upward = true, path = path, limit = math.huge })
|
||||||
for _, top_trash_dir in ipairs(top_trash_dirs) do
|
for _, top_trash_dir in ipairs(top_trash_dirs) do
|
||||||
local stat = uv.fs_lstat(top_trash_dir)
|
local stat = uv.fs_lstat(top_trash_dir)
|
||||||
if stat and not dev then
|
if stat and not dev then
|
||||||
dev = stat.dev
|
dev = stat.dev
|
||||||
end
|
end
|
||||||
if stat and stat.dev == dev and stat.type == 'directory' and is_sticky(stat.mode) then
|
if stat and stat.dev == dev and stat.type == "directory" and is_sticky(stat.mode) then
|
||||||
local trash_dir = fs.join(top_trash_dir, tostring(uv.getuid()))
|
local trash_dir = fs.join(top_trash_dir, tostring(uv.getuid()))
|
||||||
ensure_trash_dir(trash_dir)
|
ensure_trash_dir(trash_dir)
|
||||||
table.insert(dirs, trash_dir)
|
table.insert(dirs, trash_dir)
|
||||||
|
|
@ -58,7 +58,7 @@ local function get_top_trash_dirs(path)
|
||||||
|
|
||||||
-- Also search for the .Trash-$uid
|
-- Also search for the .Trash-$uid
|
||||||
top_trash_dirs = vim.fs.find(
|
top_trash_dirs = vim.fs.find(
|
||||||
string.format('.Trash-%d', uv.getuid()),
|
string.format(".Trash-%d", uv.getuid()),
|
||||||
{ upward = true, path = path, limit = math.huge }
|
{ upward = true, path = path, limit = math.huge }
|
||||||
)
|
)
|
||||||
for _, top_trash_dir in ipairs(top_trash_dirs) do
|
for _, top_trash_dir in ipairs(top_trash_dirs) do
|
||||||
|
|
@ -91,14 +91,14 @@ local function get_write_trash_dir(path)
|
||||||
return top_trash_dirs[1]
|
return top_trash_dirs[1]
|
||||||
end
|
end
|
||||||
|
|
||||||
local parent = vim.fn.fnamemodify(path, ':h')
|
local parent = vim.fn.fnamemodify(path, ":h")
|
||||||
local next_parent = vim.fn.fnamemodify(parent, ':h')
|
local next_parent = vim.fn.fnamemodify(parent, ":h")
|
||||||
while parent ~= next_parent and uv.fs_lstat(next_parent).dev == dev do
|
while parent ~= next_parent and uv.fs_lstat(next_parent).dev == dev do
|
||||||
parent = next_parent
|
parent = next_parent
|
||||||
next_parent = vim.fn.fnamemodify(parent, ':h')
|
next_parent = vim.fn.fnamemodify(parent, ":h")
|
||||||
end
|
end
|
||||||
|
|
||||||
local top_trash = fs.join(parent, string.format('.Trash-%d', uv.getuid()))
|
local top_trash = fs.join(parent, string.format(".Trash-%d", uv.getuid()))
|
||||||
ensure_trash_dir(top_trash)
|
ensure_trash_dir(top_trash)
|
||||||
return top_trash
|
return top_trash
|
||||||
end
|
end
|
||||||
|
|
@ -116,7 +116,7 @@ end
|
||||||
M.normalize_url = function(url, callback)
|
M.normalize_url = function(url, callback)
|
||||||
local scheme, path = util.parse_url(url)
|
local scheme, path = util.parse_url(url)
|
||||||
assert(path)
|
assert(path)
|
||||||
local os_path = vim.fn.fnamemodify(fs.posix_to_os_path(path), ':p')
|
local os_path = vim.fn.fnamemodify(fs.posix_to_os_path(path), ":p")
|
||||||
uv.fs_realpath(
|
uv.fs_realpath(
|
||||||
os_path,
|
os_path,
|
||||||
vim.schedule_wrap(function(err, new_os_path)
|
vim.schedule_wrap(function(err, new_os_path)
|
||||||
|
|
@ -127,12 +127,12 @@ M.normalize_url = function(url, callback)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param url string
|
---@param url string
|
||||||
---@param entry canola.Entry
|
---@param entry oil.Entry
|
||||||
---@param cb fun(path: string)
|
---@param cb fun(path: string)
|
||||||
M.get_entry_path = function(url, entry, cb)
|
M.get_entry_path = function(url, entry, cb)
|
||||||
local internal_entry = assert(cache.get_entry_by_id(entry.id))
|
local internal_entry = assert(cache.get_entry_by_id(entry.id))
|
||||||
local meta = assert(internal_entry[FIELD_META])
|
local meta = assert(internal_entry[FIELD_META])
|
||||||
---@type canola.TrashInfo
|
---@type oil.TrashInfo
|
||||||
local trash_info = meta.trash_info
|
local trash_info = meta.trash_info
|
||||||
if not trash_info then
|
if not trash_info then
|
||||||
-- This is a subpath in the trash
|
-- This is a subpath in the trash
|
||||||
|
|
@ -140,13 +140,13 @@ M.get_entry_path = function(url, entry, cb)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local path = fs.os_to_posix_path(trash_info.trash_file)
|
local path = fs.os_to_posix_path(trash_info.trash_file)
|
||||||
if meta.stat.type == 'directory' then
|
if meta.stat.type == "directory" then
|
||||||
path = util.addslash(path)
|
path = util.addslash(path)
|
||||||
end
|
end
|
||||||
cb('canola://' .. path)
|
cb("oil://" .. path)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class canola.TrashInfo
|
---@class oil.TrashInfo
|
||||||
---@field trash_file string
|
---@field trash_file string
|
||||||
---@field info_file string
|
---@field info_file string
|
||||||
---@field original_path string
|
---@field original_path string
|
||||||
|
|
@ -154,12 +154,12 @@ end
|
||||||
---@field stat uv.aliases.fs_stat_table
|
---@field stat uv.aliases.fs_stat_table
|
||||||
|
|
||||||
---@param info_file string
|
---@param info_file string
|
||||||
---@param cb fun(err?: string, info?: canola.TrashInfo)
|
---@param cb fun(err?: string, info?: oil.TrashInfo)
|
||||||
local function read_trash_info(info_file, cb)
|
local function read_trash_info(info_file, cb)
|
||||||
if not vim.endswith(info_file, '.trashinfo') then
|
if not vim.endswith(info_file, ".trashinfo") then
|
||||||
return cb('File is not .trashinfo')
|
return cb("File is not .trashinfo")
|
||||||
end
|
end
|
||||||
uv.fs_open(info_file, 'r', 448, function(err, fd)
|
uv.fs_open(info_file, "r", 448, function(err, fd)
|
||||||
if err then
|
if err then
|
||||||
return cb(err)
|
return cb(err)
|
||||||
end
|
end
|
||||||
|
|
@ -182,35 +182,35 @@ local function read_trash_info(info_file, cb)
|
||||||
local trash_info = {
|
local trash_info = {
|
||||||
info_file = info_file,
|
info_file = info_file,
|
||||||
}
|
}
|
||||||
local lines = vim.split(content, '\r?\n')
|
local lines = vim.split(content, "\r?\n")
|
||||||
if lines[1] ~= '[Trash Info]' then
|
if lines[1] ~= "[Trash Info]" then
|
||||||
return cb('File missing [Trash Info] header')
|
return cb("File missing [Trash Info] header")
|
||||||
end
|
end
|
||||||
local trash_base = vim.fn.fnamemodify(info_file, ':h:h')
|
local trash_base = vim.fn.fnamemodify(info_file, ":h:h")
|
||||||
for _, line in ipairs(lines) do
|
for _, line in ipairs(lines) do
|
||||||
local key, value = unpack(vim.split(line, '=', { plain = true, trimempty = true }))
|
local key, value = unpack(vim.split(line, "=", { plain = true, trimempty = true }))
|
||||||
if key == 'Path' and not trash_info.original_path then
|
if key == "Path" and not trash_info.original_path then
|
||||||
if not vim.startswith(value, '/') then
|
if not vim.startswith(value, "/") then
|
||||||
value = fs.join(trash_base, value)
|
value = fs.join(trash_base, value)
|
||||||
end
|
end
|
||||||
trash_info.original_path = value
|
trash_info.original_path = value
|
||||||
elseif key == 'DeletionDate' and not trash_info.deletion_date then
|
elseif key == "DeletionDate" and not trash_info.deletion_date then
|
||||||
trash_info.deletion_date = vim.fn.strptime('%Y-%m-%dT%H:%M:%S', value)
|
trash_info.deletion_date = vim.fn.strptime("%Y-%m-%dT%H:%M:%S", value)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if not trash_info.original_path or not trash_info.deletion_date then
|
if not trash_info.original_path or not trash_info.deletion_date then
|
||||||
return cb('File missing required fields')
|
return cb("File missing required fields")
|
||||||
end
|
end
|
||||||
|
|
||||||
local basename = vim.fn.fnamemodify(info_file, ':t:r')
|
local basename = vim.fn.fnamemodify(info_file, ":t:r")
|
||||||
trash_info.trash_file = fs.join(trash_base, 'files', basename)
|
trash_info.trash_file = fs.join(trash_base, "files", basename)
|
||||||
uv.fs_lstat(trash_info.trash_file, function(trash_stat_err, trash_stat)
|
uv.fs_lstat(trash_info.trash_file, function(trash_stat_err, trash_stat)
|
||||||
if trash_stat_err then
|
if trash_stat_err then
|
||||||
cb('.trashinfo file points to non-existant file')
|
cb(".trashinfo file points to non-existant file")
|
||||||
else
|
else
|
||||||
trash_info.stat = trash_stat
|
trash_info.stat = trash_stat
|
||||||
---@cast trash_info canola.TrashInfo
|
---@cast trash_info oil.TrashInfo
|
||||||
cb(nil, trash_info)
|
cb(nil, trash_info)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
@ -222,7 +222,7 @@ end
|
||||||
|
|
||||||
---@param url string
|
---@param url string
|
||||||
---@param column_defs string[]
|
---@param column_defs string[]
|
||||||
---@param cb fun(err?: string, entries?: canola.InternalEntry[], fetch_more?: fun())
|
---@param cb fun(err?: string, entries?: oil.InternalEntry[], fetch_more?: fun())
|
||||||
M.list = function(url, column_defs, cb)
|
M.list = function(url, column_defs, cb)
|
||||||
cb = vim.schedule_wrap(cb)
|
cb = vim.schedule_wrap(cb)
|
||||||
local _, path = util.parse_url(url)
|
local _, path = util.parse_url(url)
|
||||||
|
|
@ -244,14 +244,14 @@ M.list = function(url, column_defs, cb)
|
||||||
-- The first trash dir is a special case; it is in the home directory and we should only show
|
-- The first trash dir is a special case; it is in the home directory and we should only show
|
||||||
-- all entries if we are in the top root path "/"
|
-- all entries if we are in the top root path "/"
|
||||||
if trash_idx == 1 then
|
if trash_idx == 1 then
|
||||||
show_all_files = path == '/'
|
show_all_files = path == "/"
|
||||||
end
|
end
|
||||||
|
|
||||||
local info_dir = fs.join(trash_dir, 'info')
|
local info_dir = fs.join(trash_dir, "info")
|
||||||
---@diagnostic disable-next-line: param-type-mismatch, discard-returns
|
---@diagnostic disable-next-line: param-type-mismatch, discard-returns
|
||||||
uv.fs_opendir(info_dir, function(open_err, fd)
|
uv.fs_opendir(info_dir, function(open_err, fd)
|
||||||
if open_err then
|
if open_err then
|
||||||
if open_err:match('^ENOENT: no such file or directory') then
|
if open_err:match("^ENOENT: no such file or directory") then
|
||||||
-- If the directory doesn't exist, treat the list as a success. We will be able to traverse
|
-- If the directory doesn't exist, treat the list as a success. We will be able to traverse
|
||||||
-- and edit a not-yet-existing directory.
|
-- and edit a not-yet-existing directory.
|
||||||
return read_next_trash_dir()
|
return read_next_trash_dir()
|
||||||
|
|
@ -286,12 +286,12 @@ M.list = function(url, column_defs, cb)
|
||||||
-- files.
|
-- files.
|
||||||
poll()
|
poll()
|
||||||
else
|
else
|
||||||
local parent = util.addslash(vim.fn.fnamemodify(info.original_path, ':h'))
|
local parent = util.addslash(vim.fn.fnamemodify(info.original_path, ":h"))
|
||||||
if path == parent or show_all_files then
|
if path == parent or show_all_files then
|
||||||
local name = vim.fn.fnamemodify(info.trash_file, ':t')
|
local name = vim.fn.fnamemodify(info.trash_file, ":t")
|
||||||
---@diagnostic disable-next-line: undefined-field
|
---@diagnostic disable-next-line: undefined-field
|
||||||
local cache_entry = cache.create_entry(url, name, info.stat.type)
|
local cache_entry = cache.create_entry(url, name, info.stat.type)
|
||||||
local display_name = vim.fn.fnamemodify(info.original_path, ':t')
|
local display_name = vim.fn.fnamemodify(info.original_path, ":t")
|
||||||
cache_entry[FIELD_META] = {
|
cache_entry[FIELD_META] = {
|
||||||
stat = info.stat,
|
stat = info.stat,
|
||||||
trash_info = info,
|
trash_info = info,
|
||||||
|
|
@ -302,12 +302,12 @@ M.list = function(url, column_defs, cb)
|
||||||
if path ~= parent and (show_all_files or fs.is_subpath(path, parent)) then
|
if path ~= parent and (show_all_files or fs.is_subpath(path, parent)) then
|
||||||
local name = parent:sub(path:len() + 1)
|
local name = parent:sub(path:len() + 1)
|
||||||
local next_par = vim.fs.dirname(name)
|
local next_par = vim.fs.dirname(name)
|
||||||
while next_par ~= '.' do
|
while next_par ~= "." do
|
||||||
name = next_par
|
name = next_par
|
||||||
next_par = vim.fs.dirname(name)
|
next_par = vim.fs.dirname(name)
|
||||||
end
|
end
|
||||||
---@diagnostic disable-next-line: undefined-field
|
---@diagnostic disable-next-line: undefined-field
|
||||||
local cache_entry = cache.create_entry(url, name, 'directory')
|
local cache_entry = cache.create_entry(url, name, "directory")
|
||||||
|
|
||||||
cache_entry[FIELD_META] = {
|
cache_entry[FIELD_META] = {
|
||||||
stat = info.stat,
|
stat = info.stat,
|
||||||
|
|
@ -348,7 +348,7 @@ local file_columns = {}
|
||||||
local current_year
|
local current_year
|
||||||
-- Make sure we run this import-time effect in the main loop (mostly for tests)
|
-- Make sure we run this import-time effect in the main loop (mostly for tests)
|
||||||
vim.schedule(function()
|
vim.schedule(function()
|
||||||
current_year = vim.fn.strftime('%Y')
|
current_year = vim.fn.strftime("%Y")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
file_columns.mtime = {
|
file_columns.mtime = {
|
||||||
|
|
@ -357,7 +357,7 @@ file_columns.mtime = {
|
||||||
if not meta then
|
if not meta then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
---@type canola.TrashInfo
|
---@type oil.TrashInfo
|
||||||
local trash_info = meta.trash_info
|
local trash_info = meta.trash_info
|
||||||
local time = trash_info and trash_info.deletion_date or meta.stat and meta.stat.mtime.sec
|
local time = trash_info and trash_info.deletion_date or meta.stat and meta.stat.mtime.sec
|
||||||
if not time then
|
if not time then
|
||||||
|
|
@ -368,11 +368,11 @@ file_columns.mtime = {
|
||||||
if fmt then
|
if fmt then
|
||||||
ret = vim.fn.strftime(fmt, time)
|
ret = vim.fn.strftime(fmt, time)
|
||||||
else
|
else
|
||||||
local year = vim.fn.strftime('%Y', time)
|
local year = vim.fn.strftime("%Y", time)
|
||||||
if year ~= current_year then
|
if year ~= current_year then
|
||||||
ret = vim.fn.strftime('%b %d %Y', time)
|
ret = vim.fn.strftime("%b %d %Y", time)
|
||||||
else
|
else
|
||||||
ret = vim.fn.strftime('%b %d %H:%M', time)
|
ret = vim.fn.strftime("%b %d %H:%M", time)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return ret
|
return ret
|
||||||
|
|
@ -380,7 +380,7 @@ file_columns.mtime = {
|
||||||
|
|
||||||
get_sort_value = function(entry)
|
get_sort_value = function(entry)
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
---@type nil|canola.TrashInfo
|
---@type nil|oil.TrashInfo
|
||||||
local trash_info = meta and meta.trash_info
|
local trash_info = meta and meta.trash_info
|
||||||
if trash_info then
|
if trash_info then
|
||||||
return trash_info.deletion_date
|
return trash_info.deletion_date
|
||||||
|
|
@ -393,105 +393,104 @@ file_columns.mtime = {
|
||||||
local fmt = conf and conf.format
|
local fmt = conf and conf.format
|
||||||
local pattern
|
local pattern
|
||||||
if fmt then
|
if fmt then
|
||||||
pattern = fmt:gsub('%%.', '%%S+')
|
pattern = fmt:gsub("%%.", "%%S+")
|
||||||
else
|
else
|
||||||
pattern = '%S+%s+%d+%s+%d%d:?%d%d'
|
pattern = "%S+%s+%d+%s+%d%d:?%d%d"
|
||||||
end
|
end
|
||||||
return line:match('^(' .. pattern .. ')%s+(.+)$')
|
return line:match("^(" .. pattern .. ")%s+(.+)$")
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
---@param name string
|
---@param name string
|
||||||
---@return nil|canola.ColumnDefinition
|
---@return nil|oil.ColumnDefinition
|
||||||
M.get_column = function(name)
|
M.get_column = function(name)
|
||||||
return file_columns[name]
|
return file_columns[name]
|
||||||
end
|
end
|
||||||
|
|
||||||
M.supported_cross_adapter_actions = { files = 'move' }
|
M.supported_cross_adapter_actions = { files = "move" }
|
||||||
|
|
||||||
---@param action canola.Action
|
---@param action oil.Action
|
||||||
---@return boolean
|
---@return boolean
|
||||||
M.filter_action = function(action)
|
M.filter_action = function(action)
|
||||||
if action.type == 'create' then
|
if action.type == "create" then
|
||||||
return false
|
return false
|
||||||
elseif action.type == 'delete' then
|
elseif action.type == "delete" then
|
||||||
local entry = assert(cache.get_entry_by_url(action.url))
|
local entry = assert(cache.get_entry_by_url(action.url))
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
return meta ~= nil and meta.trash_info ~= nil
|
return meta ~= nil and meta.trash_info ~= nil
|
||||||
elseif action.type == 'move' then
|
elseif action.type == "move" then
|
||||||
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
||||||
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
||||||
return src_adapter.name == 'files' or dest_adapter.name == 'files'
|
return src_adapter.name == "files" or dest_adapter.name == "files"
|
||||||
-- selene: allow(if_same_then_else)
|
elseif action.type == "copy" then
|
||||||
elseif action.type == 'copy' then
|
|
||||||
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
||||||
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
||||||
return src_adapter.name == 'files' or dest_adapter.name == 'files'
|
return src_adapter.name == "files" or dest_adapter.name == "files"
|
||||||
else
|
else
|
||||||
error(string.format("Bad action type '%s'", action.type))
|
error(string.format("Bad action type '%s'", action.type))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param err canola.ParseError
|
---@param err oil.ParseError
|
||||||
---@return boolean
|
---@return boolean
|
||||||
M.filter_error = function(err)
|
M.filter_error = function(err)
|
||||||
if err.message == 'Duplicate filename' then
|
if err.message == "Duplicate filename" then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param action canola.Action
|
---@param action oil.Action
|
||||||
---@return string
|
---@return string
|
||||||
M.render_action = function(action)
|
M.render_action = function(action)
|
||||||
if action.type == 'delete' then
|
if action.type == "delete" then
|
||||||
local entry = assert(cache.get_entry_by_url(action.url))
|
local entry = assert(cache.get_entry_by_url(action.url))
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
---@type canola.TrashInfo
|
---@type oil.TrashInfo
|
||||||
local trash_info = assert(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
|
||||||
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
||||||
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
||||||
if src_adapter.name == 'files' then
|
if src_adapter.name == "files" then
|
||||||
local _, path = util.parse_url(action.src_url)
|
local _, path = util.parse_url(action.src_url)
|
||||||
assert(path)
|
assert(path)
|
||||||
local short_path = files.to_short_os_path(path, action.entry_type)
|
local short_path = files.to_short_os_path(path, action.entry_type)
|
||||||
return string.format(' TRASH %s', short_path)
|
return string.format(" TRASH %s", short_path)
|
||||||
elseif dest_adapter.name == 'files' then
|
elseif dest_adapter.name == "files" then
|
||||||
local _, path = util.parse_url(action.dest_url)
|
local _, path = util.parse_url(action.dest_url)
|
||||||
assert(path)
|
assert(path)
|
||||||
local short_path = files.to_short_os_path(path, action.entry_type)
|
local short_path = files.to_short_os_path(path, action.entry_type)
|
||||||
return string.format('RESTORE %s', short_path)
|
return string.format("RESTORE %s", short_path)
|
||||||
else
|
else
|
||||||
error('Must be moving files into or out of trash')
|
error("Must be moving files into or out of trash")
|
||||||
end
|
end
|
||||||
elseif action.type == 'copy' then
|
elseif action.type == "copy" then
|
||||||
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
||||||
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
||||||
if src_adapter.name == 'files' then
|
if src_adapter.name == "files" then
|
||||||
local _, path = util.parse_url(action.src_url)
|
local _, path = util.parse_url(action.src_url)
|
||||||
assert(path)
|
assert(path)
|
||||||
local short_path = files.to_short_os_path(path, action.entry_type)
|
local short_path = files.to_short_os_path(path, action.entry_type)
|
||||||
return string.format(' COPY %s -> TRASH', short_path)
|
return string.format(" COPY %s -> TRASH", short_path)
|
||||||
elseif dest_adapter.name == 'files' then
|
elseif dest_adapter.name == "files" then
|
||||||
local _, path = util.parse_url(action.dest_url)
|
local _, path = util.parse_url(action.dest_url)
|
||||||
assert(path)
|
assert(path)
|
||||||
local short_path = files.to_short_os_path(path, action.entry_type)
|
local short_path = files.to_short_os_path(path, action.entry_type)
|
||||||
return string.format('RESTORE %s', short_path)
|
return string.format("RESTORE %s", short_path)
|
||||||
else
|
else
|
||||||
error('Must be copying files into or out of trash')
|
error("Must be copying files into or out of trash")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
error(string.format("Bad action type '%s'", action.type))
|
error(string.format("Bad action type '%s'", action.type))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param trash_info canola.TrashInfo
|
---@param trash_info oil.TrashInfo
|
||||||
---@param cb fun(err?: string)
|
---@param cb fun(err?: string)
|
||||||
local function purge(trash_info, cb)
|
local function purge(trash_info, cb)
|
||||||
fs.recursive_delete('file', trash_info.info_file, function(err)
|
fs.recursive_delete("file", trash_info.info_file, function(err)
|
||||||
if err then
|
if err then
|
||||||
return cb(err)
|
return cb(err)
|
||||||
end
|
end
|
||||||
|
|
@ -506,15 +505,15 @@ end
|
||||||
local function write_info_file(path, info_path, cb)
|
local function write_info_file(path, info_path, cb)
|
||||||
uv.fs_open(
|
uv.fs_open(
|
||||||
info_path,
|
info_path,
|
||||||
'w',
|
"w",
|
||||||
448,
|
448,
|
||||||
vim.schedule_wrap(function(err, fd)
|
vim.schedule_wrap(function(err, fd)
|
||||||
if err then
|
if err then
|
||||||
return cb(err)
|
return cb(err)
|
||||||
end
|
end
|
||||||
assert(fd)
|
assert(fd)
|
||||||
local deletion_date = vim.fn.strftime('%Y-%m-%dT%H:%M:%S')
|
local deletion_date = vim.fn.strftime("%Y-%m-%dT%H:%M:%S")
|
||||||
local contents = string.format('[Trash Info]\nPath=%s\nDeletionDate=%s', path, deletion_date)
|
local contents = string.format("[Trash Info]\nPath=%s\nDeletionDate=%s", path, deletion_date)
|
||||||
uv.fs_write(fd, contents, function(write_err)
|
uv.fs_write(fd, contents, function(write_err)
|
||||||
uv.fs_close(fd, function(close_err)
|
uv.fs_close(fd, function(close_err)
|
||||||
cb(write_err or close_err)
|
cb(write_err or close_err)
|
||||||
|
|
@ -525,14 +524,14 @@ local function write_info_file(path, info_path, cb)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param path string
|
---@param path string
|
||||||
---@param cb fun(err?: string, trash_info?: canola.TrashInfo)
|
---@param cb fun(err?: string, trash_info?: oil.TrashInfo)
|
||||||
local function create_trash_info(path, cb)
|
local function create_trash_info(path, cb)
|
||||||
local trash_dir = get_write_trash_dir(path)
|
local trash_dir = get_write_trash_dir(path)
|
||||||
local basename = vim.fs.basename(path)
|
local basename = vim.fs.basename(path)
|
||||||
local now = os.time()
|
local now = os.time()
|
||||||
local name = string.format('%s-%d.%d', basename, now, math.random(100000, 999999))
|
local name = string.format("%s-%d.%d", basename, now, math.random(100000, 999999))
|
||||||
local dest_path = fs.join(trash_dir, 'files', name)
|
local dest_path = fs.join(trash_dir, "files", name)
|
||||||
local dest_info = fs.join(trash_dir, 'info', name .. '.trashinfo')
|
local dest_info = fs.join(trash_dir, "info", name .. ".trashinfo")
|
||||||
uv.fs_lstat(path, function(err, stat)
|
uv.fs_lstat(path, function(err, stat)
|
||||||
if err then
|
if err then
|
||||||
return cb(err)
|
return cb(err)
|
||||||
|
|
@ -542,7 +541,7 @@ local function create_trash_info(path, cb)
|
||||||
if info_err then
|
if info_err then
|
||||||
return cb(info_err)
|
return cb(info_err)
|
||||||
end
|
end
|
||||||
---@type canola.TrashInfo
|
---@type oil.TrashInfo
|
||||||
local trash_info = {
|
local trash_info = {
|
||||||
original_path = path,
|
original_path = path,
|
||||||
trash_file = dest_path,
|
trash_file = dest_path,
|
||||||
|
|
@ -555,28 +554,28 @@ local function create_trash_info(path, cb)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param action canola.Action
|
---@param action oil.Action
|
||||||
---@param cb fun(err: nil|string)
|
---@param cb fun(err: nil|string)
|
||||||
M.perform_action = function(action, cb)
|
M.perform_action = function(action, cb)
|
||||||
if action.type == 'delete' then
|
if action.type == "delete" then
|
||||||
local entry = assert(cache.get_entry_by_url(action.url))
|
local entry = assert(cache.get_entry_by_url(action.url))
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
---@type canola.TrashInfo
|
---@type oil.TrashInfo
|
||||||
local trash_info = assert(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))
|
||||||
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 src_adapter.name == 'files' then
|
if src_adapter.name == "files" then
|
||||||
local _, path = util.parse_url(action.src_url)
|
local _, path = util.parse_url(action.src_url)
|
||||||
M.delete_to_trash(assert(path), cb)
|
M.delete_to_trash(assert(path), cb)
|
||||||
elseif dest_adapter.name == 'files' then
|
elseif dest_adapter.name == "files" then
|
||||||
-- Restore
|
-- Restore
|
||||||
local _, dest_path = util.parse_url(action.dest_url)
|
local _, dest_path = util.parse_url(action.dest_url)
|
||||||
assert(dest_path)
|
assert(dest_path)
|
||||||
local entry = assert(cache.get_entry_by_url(action.src_url))
|
local entry = assert(cache.get_entry_by_url(action.src_url))
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
---@type canola.TrashInfo
|
---@type oil.TrashInfo
|
||||||
local trash_info = assert(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
|
||||||
|
|
@ -585,36 +584,36 @@ M.perform_action = function(action, cb)
|
||||||
uv.fs_unlink(trash_info.info_file, cb)
|
uv.fs_unlink(trash_info.info_file, cb)
|
||||||
end)
|
end)
|
||||||
else
|
else
|
||||||
error('Must be moving files into or out of trash')
|
error("Must be moving files into or out of trash")
|
||||||
end
|
end
|
||||||
elseif action.type == 'copy' then
|
elseif action.type == "copy" then
|
||||||
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
||||||
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
||||||
if src_adapter.name == 'files' then
|
if src_adapter.name == "files" then
|
||||||
local _, path = util.parse_url(action.src_url)
|
local _, path = util.parse_url(action.src_url)
|
||||||
assert(path)
|
assert(path)
|
||||||
create_trash_info(path, function(err, trash_info)
|
create_trash_info(path, function(err, trash_info)
|
||||||
if err then
|
if err then
|
||||||
cb(err)
|
cb(err)
|
||||||
else
|
else
|
||||||
local stat_type = trash_info.stat.type or 'unknown'
|
local stat_type = trash_info.stat.type or "unknown"
|
||||||
fs.recursive_copy(stat_type, path, trash_info.trash_file, vim.schedule_wrap(cb))
|
fs.recursive_copy(stat_type, path, trash_info.trash_file, vim.schedule_wrap(cb))
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
elseif dest_adapter.name == 'files' then
|
elseif dest_adapter.name == "files" then
|
||||||
-- Restore
|
-- Restore
|
||||||
local _, dest_path = util.parse_url(action.dest_url)
|
local _, dest_path = util.parse_url(action.dest_url)
|
||||||
assert(dest_path)
|
assert(dest_path)
|
||||||
local entry = assert(cache.get_entry_by_url(action.src_url))
|
local entry = assert(cache.get_entry_by_url(action.src_url))
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
---@type canola.TrashInfo
|
---@type oil.TrashInfo
|
||||||
local trash_info = assert(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")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
cb(string.format('Bad action type: %s', action.type))
|
cb(string.format("Bad action type: %s", action.type))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -625,7 +624,7 @@ M.delete_to_trash = function(path, cb)
|
||||||
if err then
|
if err then
|
||||||
cb(err)
|
cb(err)
|
||||||
else
|
else
|
||||||
local stat_type = trash_info.stat.type or 'unknown'
|
local stat_type = trash_info.stat.type or "unknown"
|
||||||
fs.recursive_move(stat_type, path, trash_info.trash_file, vim.schedule_wrap(cb))
|
fs.recursive_move(stat_type, path, trash_info.trash_file, vim.schedule_wrap(cb))
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
local cache = require('canola.cache')
|
local cache = require("oil.cache")
|
||||||
local config = require('canola.config')
|
local config = require("oil.config")
|
||||||
local files = require('canola.adapters.files')
|
local files = require("oil.adapters.files")
|
||||||
local fs = require('canola.fs')
|
local fs = require("oil.fs")
|
||||||
local util = require('canola.util')
|
local util = require("oil.util")
|
||||||
|
|
||||||
local uv = vim.uv or vim.loop
|
local uv = vim.uv or vim.loop
|
||||||
|
|
||||||
|
|
@ -15,7 +15,7 @@ end
|
||||||
---Gets the location of the home trash dir, creating it if necessary
|
---Gets the location of the home trash dir, creating it if necessary
|
||||||
---@return string
|
---@return string
|
||||||
local function get_trash_dir()
|
local function get_trash_dir()
|
||||||
local trash_dir = fs.join(assert(uv.os_homedir()), '.Trash')
|
local trash_dir = fs.join(assert(uv.os_homedir()), ".Trash")
|
||||||
touch_dir(trash_dir)
|
touch_dir(trash_dir)
|
||||||
return trash_dir
|
return trash_dir
|
||||||
end
|
end
|
||||||
|
|
@ -25,24 +25,24 @@ end
|
||||||
M.normalize_url = function(url, callback)
|
M.normalize_url = function(url, callback)
|
||||||
local scheme, path = util.parse_url(url)
|
local scheme, path = util.parse_url(url)
|
||||||
assert(path)
|
assert(path)
|
||||||
callback(scheme .. '/')
|
callback(scheme .. "/")
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param url string
|
---@param url string
|
||||||
---@param entry canola.Entry
|
---@param entry oil.Entry
|
||||||
---@param cb fun(path: string)
|
---@param cb fun(path: string)
|
||||||
M.get_entry_path = function(url, entry, cb)
|
M.get_entry_path = function(url, entry, cb)
|
||||||
local trash_dir = get_trash_dir()
|
local trash_dir = get_trash_dir()
|
||||||
local path = fs.join(trash_dir, entry.name)
|
local path = fs.join(trash_dir, entry.name)
|
||||||
if entry.type == 'directory' then
|
if entry.type == "directory" then
|
||||||
path = 'canola://' .. path
|
path = "oil://" .. path
|
||||||
end
|
end
|
||||||
cb(path)
|
cb(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param url string
|
---@param url string
|
||||||
---@param column_defs string[]
|
---@param column_defs string[]
|
||||||
---@param cb fun(err?: string, entries?: canola.InternalEntry[], fetch_more?: fun())
|
---@param cb fun(err?: string, entries?: oil.InternalEntry[], fetch_more?: fun())
|
||||||
M.list = function(url, column_defs, cb)
|
M.list = function(url, column_defs, cb)
|
||||||
cb = vim.schedule_wrap(cb)
|
cb = vim.schedule_wrap(cb)
|
||||||
local _, path = util.parse_url(url)
|
local _, path = util.parse_url(url)
|
||||||
|
|
@ -51,7 +51,7 @@ M.list = function(url, column_defs, cb)
|
||||||
---@diagnostic disable-next-line: param-type-mismatch, discard-returns
|
---@diagnostic disable-next-line: param-type-mismatch, discard-returns
|
||||||
uv.fs_opendir(trash_dir, function(open_err, fd)
|
uv.fs_opendir(trash_dir, function(open_err, fd)
|
||||||
if open_err then
|
if open_err then
|
||||||
if open_err:match('^ENOENT: no such file or directory') then
|
if open_err:match("^ENOENT: no such file or directory") then
|
||||||
-- If the directory doesn't exist, treat the list as a success. We will be able to traverse
|
-- If the directory doesn't exist, treat the list as a success. We will be able to traverse
|
||||||
-- and edit a not-yet-existing directory.
|
-- and edit a not-yet-existing directory.
|
||||||
return cb()
|
return cb()
|
||||||
|
|
@ -106,61 +106,61 @@ M.is_modifiable = function(bufnr)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param name string
|
---@param name string
|
||||||
---@return nil|canola.ColumnDefinition
|
---@return nil|oil.ColumnDefinition
|
||||||
M.get_column = function(name)
|
M.get_column = function(name)
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
M.supported_cross_adapter_actions = { files = 'move' }
|
M.supported_cross_adapter_actions = { files = "move" }
|
||||||
|
|
||||||
---@param action canola.Action
|
---@param action oil.Action
|
||||||
---@return string
|
---@return string
|
||||||
M.render_action = function(action)
|
M.render_action = function(action)
|
||||||
if action.type == 'create' then
|
if action.type == "create" then
|
||||||
return string.format('CREATE %s', action.url)
|
return string.format("CREATE %s", action.url)
|
||||||
elseif action.type == 'delete' then
|
elseif action.type == "delete" then
|
||||||
return string.format(' PURGE %s', action.url)
|
return string.format(" PURGE %s", action.url)
|
||||||
elseif action.type == 'move' then
|
elseif action.type == "move" then
|
||||||
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
||||||
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
||||||
if src_adapter.name == 'files' then
|
if src_adapter.name == "files" then
|
||||||
local _, path = util.parse_url(action.src_url)
|
local _, path = util.parse_url(action.src_url)
|
||||||
assert(path)
|
assert(path)
|
||||||
local short_path = files.to_short_os_path(path, action.entry_type)
|
local short_path = files.to_short_os_path(path, action.entry_type)
|
||||||
return string.format(' TRASH %s', short_path)
|
return string.format(" TRASH %s", short_path)
|
||||||
elseif dest_adapter.name == 'files' then
|
elseif dest_adapter.name == "files" then
|
||||||
local _, path = util.parse_url(action.dest_url)
|
local _, path = util.parse_url(action.dest_url)
|
||||||
assert(path)
|
assert(path)
|
||||||
local short_path = files.to_short_os_path(path, action.entry_type)
|
local short_path = files.to_short_os_path(path, action.entry_type)
|
||||||
return string.format('RESTORE %s', short_path)
|
return string.format("RESTORE %s", short_path)
|
||||||
else
|
else
|
||||||
return string.format(' %s %s -> %s', action.type:upper(), action.src_url, action.dest_url)
|
return string.format(" %s %s -> %s", action.type:upper(), action.src_url, action.dest_url)
|
||||||
end
|
end
|
||||||
elseif action.type == 'copy' then
|
elseif action.type == "copy" then
|
||||||
return string.format(' %s %s -> %s', action.type:upper(), action.src_url, action.dest_url)
|
return string.format(" %s %s -> %s", action.type:upper(), action.src_url, action.dest_url)
|
||||||
else
|
else
|
||||||
error('Bad action type')
|
error("Bad action type")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param action canola.Action
|
---@param action oil.Action
|
||||||
---@param cb fun(err: nil|string)
|
---@param cb fun(err: nil|string)
|
||||||
M.perform_action = function(action, cb)
|
M.perform_action = function(action, cb)
|
||||||
local trash_dir = get_trash_dir()
|
local trash_dir = get_trash_dir()
|
||||||
if action.type == 'create' then
|
if action.type == "create" then
|
||||||
local _, path = util.parse_url(action.url)
|
local _, path = util.parse_url(action.url)
|
||||||
assert(path)
|
assert(path)
|
||||||
path = trash_dir .. path
|
path = trash_dir .. path
|
||||||
if action.entry_type == 'directory' then
|
if action.entry_type == "directory" then
|
||||||
uv.fs_mkdir(path, 493, function(err)
|
uv.fs_mkdir(path, 493, function(err)
|
||||||
-- Ignore if the directory already exists
|
-- Ignore if the directory already exists
|
||||||
if not err or err:match('^EEXIST:') then
|
if not err or err:match("^EEXIST:") then
|
||||||
cb()
|
cb()
|
||||||
else
|
else
|
||||||
cb(err)
|
cb(err)
|
||||||
end
|
end
|
||||||
end) -- 0755
|
end) -- 0755
|
||||||
elseif action.entry_type == 'link' and action.link then
|
elseif action.entry_type == "link" and action.link then
|
||||||
local flags = nil
|
local flags = nil
|
||||||
local target = fs.posix_to_os_path(action.link)
|
local target = fs.posix_to_os_path(action.link)
|
||||||
---@diagnostic disable-next-line: param-type-mismatch
|
---@diagnostic disable-next-line: param-type-mismatch
|
||||||
|
|
@ -168,33 +168,33 @@ M.perform_action = function(action, cb)
|
||||||
else
|
else
|
||||||
fs.touch(path, config.new_file_mode, cb)
|
fs.touch(path, config.new_file_mode, cb)
|
||||||
end
|
end
|
||||||
elseif action.type == 'delete' then
|
elseif action.type == "delete" then
|
||||||
local _, path = util.parse_url(action.url)
|
local _, path = util.parse_url(action.url)
|
||||||
assert(path)
|
assert(path)
|
||||||
local fullpath = trash_dir .. path
|
local fullpath = trash_dir .. path
|
||||||
fs.recursive_delete(action.entry_type, fullpath, cb)
|
fs.recursive_delete(action.entry_type, fullpath, cb)
|
||||||
elseif action.type == 'move' or action.type == 'copy' then
|
elseif action.type == "move" or action.type == "copy" then
|
||||||
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
||||||
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
||||||
local _, src_path = util.parse_url(action.src_url)
|
local _, src_path = util.parse_url(action.src_url)
|
||||||
local _, dest_path = util.parse_url(action.dest_url)
|
local _, dest_path = util.parse_url(action.dest_url)
|
||||||
assert(src_path and dest_path)
|
assert(src_path and dest_path)
|
||||||
if src_adapter.name == 'files' then
|
if src_adapter.name == "files" then
|
||||||
dest_path = trash_dir .. dest_path
|
dest_path = trash_dir .. dest_path
|
||||||
elseif dest_adapter.name == 'files' then
|
elseif dest_adapter.name == "files" then
|
||||||
src_path = trash_dir .. src_path
|
src_path = trash_dir .. src_path
|
||||||
else
|
else
|
||||||
dest_path = trash_dir .. dest_path
|
dest_path = trash_dir .. dest_path
|
||||||
src_path = trash_dir .. src_path
|
src_path = trash_dir .. src_path
|
||||||
end
|
end
|
||||||
|
|
||||||
if action.type == 'move' then
|
if action.type == "move" then
|
||||||
fs.recursive_move(action.entry_type, src_path, dest_path, cb)
|
fs.recursive_move(action.entry_type, src_path, dest_path, cb)
|
||||||
else
|
else
|
||||||
fs.recursive_copy(action.entry_type, src_path, dest_path, cb)
|
fs.recursive_copy(action.entry_type, src_path, dest_path, cb)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
cb(string.format('Bad action type: %s', action.type))
|
cb(string.format("Bad action type: %s", action.type))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -212,8 +212,8 @@ M.delete_to_trash = function(path, cb)
|
||||||
end
|
end
|
||||||
assert(src_stat)
|
assert(src_stat)
|
||||||
if uv.fs_lstat(dest) then
|
if uv.fs_lstat(dest) then
|
||||||
local date_str = vim.fn.strftime(' %Y-%m-%dT%H:%M:%S')
|
local date_str = vim.fn.strftime(" %Y-%m-%dT%H:%M:%S")
|
||||||
local name_pieces = vim.split(basename, '.', { plain = true })
|
local name_pieces = vim.split(basename, ".", { plain = true })
|
||||||
if #name_pieces > 1 then
|
if #name_pieces > 1 then
|
||||||
table.insert(name_pieces, #name_pieces - 1, date_str)
|
table.insert(name_pieces, #name_pieces - 1, date_str)
|
||||||
basename = table.concat(name_pieces)
|
basename = table.concat(name_pieces)
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
local util = require('canola.util')
|
local util = require("oil.util")
|
||||||
local uv = vim.uv or vim.loop
|
local uv = vim.uv or vim.loop
|
||||||
local cache = require('canola.cache')
|
local cache = require("oil.cache")
|
||||||
local config = require('canola.config')
|
local config = require("oil.config")
|
||||||
local constants = require('canola.constants')
|
local constants = require("oil.constants")
|
||||||
local files = require('canola.adapters.files')
|
local files = require("oil.adapters.files")
|
||||||
local fs = require('canola.fs')
|
local fs = require("oil.fs")
|
||||||
local powershell_trash = require('canola.adapters.trash.windows.powershell-trash')
|
local powershell_trash = require("oil.adapters.trash.windows.powershell-trash")
|
||||||
|
|
||||||
local FIELD_META = constants.FIELD_META
|
local FIELD_META = constants.FIELD_META
|
||||||
local FIELD_TYPE = constants.FIELD_TYPE
|
local FIELD_TYPE = constants.FIELD_TYPE
|
||||||
|
|
@ -15,28 +15,28 @@ local M = {}
|
||||||
---@return string
|
---@return string
|
||||||
local function get_trash_dir()
|
local function get_trash_dir()
|
||||||
local cwd = assert(vim.fn.getcwd())
|
local cwd = assert(vim.fn.getcwd())
|
||||||
local trash_dir = cwd:sub(1, 3) .. '$Recycle.Bin'
|
local trash_dir = cwd:sub(1, 3) .. "$Recycle.Bin"
|
||||||
if vim.fn.isdirectory(trash_dir) == 1 then
|
if vim.fn.isdirectory(trash_dir) == 1 then
|
||||||
return trash_dir
|
return trash_dir
|
||||||
end
|
end
|
||||||
trash_dir = 'C:\\$Recycle.Bin'
|
trash_dir = "C:\\$Recycle.Bin"
|
||||||
if vim.fn.isdirectory(trash_dir) == 1 then
|
if vim.fn.isdirectory(trash_dir) == 1 then
|
||||||
return trash_dir
|
return trash_dir
|
||||||
end
|
end
|
||||||
error('No trash found')
|
error("No trash found")
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param path string
|
---@param path string
|
||||||
---@return string
|
---@return string
|
||||||
local win_addslash = function(path)
|
local win_addslash = function(path)
|
||||||
if not vim.endswith(path, '\\') then
|
if not vim.endswith(path, "\\") then
|
||||||
return path .. '\\'
|
return path .. "\\"
|
||||||
else
|
else
|
||||||
return path
|
return path
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class canola.WindowsTrashInfo
|
---@class oil.WindowsTrashInfo
|
||||||
---@field trash_file string
|
---@field trash_file string
|
||||||
---@field original_path string
|
---@field original_path string
|
||||||
---@field deletion_date integer
|
---@field deletion_date integer
|
||||||
|
|
@ -44,7 +44,7 @@ end
|
||||||
|
|
||||||
---@param url string
|
---@param url string
|
||||||
---@param column_defs string[]
|
---@param column_defs string[]
|
||||||
---@param cb fun(err?: string, entries?: canola.InternalEntry[], fetch_more?: fun())
|
---@param cb fun(err?: string, entries?: oil.InternalEntry[], fetch_more?: fun())
|
||||||
M.list = function(url, column_defs, cb)
|
M.list = function(url, column_defs, cb)
|
||||||
local _, path = util.parse_url(url)
|
local _, path = util.parse_url(url)
|
||||||
path = fs.posix_to_os_path(assert(path))
|
path = fs.posix_to_os_path(assert(path))
|
||||||
|
|
@ -61,7 +61,7 @@ M.list = function(url, column_defs, cb)
|
||||||
local raw_displayed_entries = vim.tbl_filter(
|
local raw_displayed_entries = vim.tbl_filter(
|
||||||
---@param entry {IsFolder: boolean, DeletionDate: integer, Name: string, Path: string, OriginalPath: string}
|
---@param entry {IsFolder: boolean, DeletionDate: integer, Name: string, Path: string, OriginalPath: string}
|
||||||
function(entry)
|
function(entry)
|
||||||
local parent = win_addslash(assert(vim.fn.fnamemodify(entry.OriginalPath, ':h')))
|
local parent = win_addslash(assert(vim.fn.fnamemodify(entry.OriginalPath, ":h")))
|
||||||
local is_in_path = path == parent
|
local is_in_path = path == parent
|
||||||
local is_subpath = fs.is_subpath(path, parent)
|
local is_subpath = fs.is_subpath(path, parent)
|
||||||
return is_in_path or is_subpath or show_all_files
|
return is_in_path or is_subpath or show_all_files
|
||||||
|
|
@ -70,33 +70,33 @@ M.list = function(url, column_defs, cb)
|
||||||
)
|
)
|
||||||
local displayed_entries = vim.tbl_map(
|
local displayed_entries = vim.tbl_map(
|
||||||
---@param entry {IsFolder: boolean, DeletionDate: integer, Name: string, Path: string, OriginalPath: string}
|
---@param entry {IsFolder: boolean, DeletionDate: integer, Name: string, Path: string, OriginalPath: string}
|
||||||
---@return {[1]:nil, [2]:string, [3]:string, [4]:{stat: uv_fs_t, trash_info: canola.WindowsTrashInfo, display_name: string}}
|
---@return {[1]:nil, [2]:string, [3]:string, [4]:{stat: uv_fs_t, trash_info: oil.WindowsTrashInfo, display_name: string}}
|
||||||
function(entry)
|
function(entry)
|
||||||
local parent = win_addslash(assert(vim.fn.fnamemodify(entry.OriginalPath, ':h')))
|
local parent = win_addslash(assert(vim.fn.fnamemodify(entry.OriginalPath, ":h")))
|
||||||
|
|
||||||
--- @type canola.InternalEntry
|
--- @type oil.InternalEntry
|
||||||
local cache_entry
|
local cache_entry
|
||||||
if path == parent or show_all_files then
|
if path == parent or show_all_files then
|
||||||
local deleted_file_tail = assert(vim.fn.fnamemodify(entry.Path, ':t'))
|
local deleted_file_tail = assert(vim.fn.fnamemodify(entry.Path, ":t"))
|
||||||
local deleted_file_head = assert(vim.fn.fnamemodify(entry.Path, ':h'))
|
local deleted_file_head = assert(vim.fn.fnamemodify(entry.Path, ":h"))
|
||||||
local info_file_head = deleted_file_head
|
local info_file_head = deleted_file_head
|
||||||
--- @type string?
|
--- @type string?
|
||||||
local info_file
|
local info_file
|
||||||
cache_entry =
|
cache_entry =
|
||||||
cache.create_entry(url, deleted_file_tail, entry.IsFolder and 'directory' or 'file')
|
cache.create_entry(url, deleted_file_tail, entry.IsFolder and "directory" or "file")
|
||||||
-- info_file on windows has the following format: $I<6 char hash>.<extension>
|
-- info_file on windows has the following format: $I<6 char hash>.<extension>
|
||||||
-- the hash is the same for the deleted file and the info file
|
-- the hash is the same for the deleted file and the info file
|
||||||
-- so, we take the hash (and extension) from the deleted file
|
-- so, we take the hash (and extension) from the deleted file
|
||||||
--
|
--
|
||||||
-- see https://superuser.com/questions/368890/how-does-the-recycle-bin-in-windows-work/1736690#1736690
|
-- see https://superuser.com/questions/368890/how-does-the-recycle-bin-in-windows-work/1736690#1736690
|
||||||
local info_file_tail = deleted_file_tail:match('^%$R(.*)$') --[[@as string?]]
|
local info_file_tail = deleted_file_tail:match("^%$R(.*)$") --[[@as string?]]
|
||||||
if info_file_tail then
|
if info_file_tail then
|
||||||
info_file_tail = '$I' .. info_file_tail
|
info_file_tail = "$I" .. info_file_tail
|
||||||
info_file = info_file_head .. '\\' .. info_file_tail
|
info_file = info_file_head .. "\\" .. info_file_tail
|
||||||
end
|
end
|
||||||
cache_entry[FIELD_META] = {
|
cache_entry[FIELD_META] = {
|
||||||
stat = nil,
|
stat = nil,
|
||||||
---@type canola.WindowsTrashInfo
|
---@type oil.WindowsTrashInfo
|
||||||
trash_info = {
|
trash_info = {
|
||||||
trash_file = entry.Path,
|
trash_file = entry.Path,
|
||||||
original_path = entry.OriginalPath,
|
original_path = entry.OriginalPath,
|
||||||
|
|
@ -109,10 +109,10 @@ M.list = function(url, column_defs, cb)
|
||||||
if path ~= parent and (show_all_files or fs.is_subpath(path, parent)) then
|
if path ~= parent and (show_all_files or fs.is_subpath(path, parent)) then
|
||||||
local name = parent:sub(path:len() + 1)
|
local name = parent:sub(path:len() + 1)
|
||||||
local next_par = vim.fs.dirname(name)
|
local next_par = vim.fs.dirname(name)
|
||||||
while next_par ~= '.' do
|
while next_par ~= "." do
|
||||||
name = next_par
|
name = next_par
|
||||||
next_par = vim.fs.dirname(name)
|
next_par = vim.fs.dirname(name)
|
||||||
cache_entry = cache.create_entry(url, name, 'directory')
|
cache_entry = cache.create_entry(url, name, "directory")
|
||||||
|
|
||||||
cache_entry[FIELD_META] = {}
|
cache_entry[FIELD_META] = {}
|
||||||
end
|
end
|
||||||
|
|
@ -132,7 +132,7 @@ end
|
||||||
local current_year
|
local current_year
|
||||||
-- Make sure we run this import-time effect in the main loop (mostly for tests)
|
-- Make sure we run this import-time effect in the main loop (mostly for tests)
|
||||||
vim.schedule(function()
|
vim.schedule(function()
|
||||||
current_year = vim.fn.strftime('%Y')
|
current_year = vim.fn.strftime("%Y")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
local file_columns = {}
|
local file_columns = {}
|
||||||
|
|
@ -142,7 +142,7 @@ file_columns.mtime = {
|
||||||
if not meta then
|
if not meta then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
---@type canola.WindowsTrashInfo
|
---@type oil.WindowsTrashInfo
|
||||||
local trash_info = meta.trash_info
|
local trash_info = meta.trash_info
|
||||||
local time = trash_info and trash_info.deletion_date
|
local time = trash_info and trash_info.deletion_date
|
||||||
if not time then
|
if not time then
|
||||||
|
|
@ -153,11 +153,11 @@ file_columns.mtime = {
|
||||||
if fmt then
|
if fmt then
|
||||||
ret = vim.fn.strftime(fmt, time)
|
ret = vim.fn.strftime(fmt, time)
|
||||||
else
|
else
|
||||||
local year = vim.fn.strftime('%Y', time)
|
local year = vim.fn.strftime("%Y", time)
|
||||||
if year ~= current_year then
|
if year ~= current_year then
|
||||||
ret = vim.fn.strftime('%b %d %Y', time)
|
ret = vim.fn.strftime("%b %d %Y", time)
|
||||||
else
|
else
|
||||||
ret = vim.fn.strftime('%b %d %H:%M', time)
|
ret = vim.fn.strftime("%b %d %H:%M", time)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return ret
|
return ret
|
||||||
|
|
@ -165,7 +165,7 @@ file_columns.mtime = {
|
||||||
|
|
||||||
get_sort_value = function(entry)
|
get_sort_value = function(entry)
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
---@type nil|canola.WindowsTrashInfo
|
---@type nil|oil.WindowsTrashInfo
|
||||||
local trash_info = meta and meta.trash_info
|
local trash_info = meta and meta.trash_info
|
||||||
if trash_info and trash_info.deletion_date then
|
if trash_info and trash_info.deletion_date then
|
||||||
return trash_info.deletion_date
|
return trash_info.deletion_date
|
||||||
|
|
@ -178,38 +178,37 @@ file_columns.mtime = {
|
||||||
local fmt = conf and conf.format
|
local fmt = conf and conf.format
|
||||||
local pattern
|
local pattern
|
||||||
if fmt then
|
if fmt then
|
||||||
pattern = fmt:gsub('%%.', '%%S+')
|
pattern = fmt:gsub("%%.", "%%S+")
|
||||||
else
|
else
|
||||||
pattern = '%S+%s+%d+%s+%d%d:?%d%d'
|
pattern = "%S+%s+%d+%s+%d%d:?%d%d"
|
||||||
end
|
end
|
||||||
return line:match('^(' .. pattern .. ')%s+(.+)$')
|
return line:match("^(" .. pattern .. ")%s+(.+)$")
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
---@param name string
|
---@param name string
|
||||||
---@return nil|canola.ColumnDefinition
|
---@return nil|oil.ColumnDefinition
|
||||||
M.get_column = function(name)
|
M.get_column = function(name)
|
||||||
return file_columns[name]
|
return file_columns[name]
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param action canola.Action
|
---@param action oil.Action
|
||||||
---@return boolean
|
---@return boolean
|
||||||
M.filter_action = function(action)
|
M.filter_action = function(action)
|
||||||
if action.type == 'create' then
|
if action.type == "create" then
|
||||||
return false
|
return false
|
||||||
elseif action.type == 'delete' then
|
elseif action.type == "delete" then
|
||||||
local entry = assert(cache.get_entry_by_url(action.url))
|
local entry = assert(cache.get_entry_by_url(action.url))
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
return meta ~= nil and meta.trash_info ~= nil
|
return meta ~= nil and meta.trash_info ~= nil
|
||||||
elseif action.type == 'move' then
|
elseif action.type == "move" then
|
||||||
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
||||||
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
||||||
return src_adapter.name == 'files' or dest_adapter.name == 'files'
|
return src_adapter.name == "files" or dest_adapter.name == "files"
|
||||||
-- selene: allow(if_same_then_else)
|
elseif action.type == "copy" then
|
||||||
elseif action.type == 'copy' then
|
|
||||||
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
||||||
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
||||||
return src_adapter.name == 'files' or dest_adapter.name == 'files'
|
return src_adapter.name == "files" or dest_adapter.name == "files"
|
||||||
else
|
else
|
||||||
error(string.format("Bad action type '%s'", action.type))
|
error(string.format("Bad action type '%s'", action.type))
|
||||||
end
|
end
|
||||||
|
|
@ -220,7 +219,7 @@ end
|
||||||
M.normalize_url = function(url, callback)
|
M.normalize_url = function(url, callback)
|
||||||
local scheme, path = util.parse_url(url)
|
local scheme, path = util.parse_url(url)
|
||||||
assert(path)
|
assert(path)
|
||||||
local os_path = vim.fn.fnamemodify(fs.posix_to_os_path(path), ':p')
|
local os_path = vim.fn.fnamemodify(fs.posix_to_os_path(path), ":p")
|
||||||
assert(os_path)
|
assert(os_path)
|
||||||
uv.fs_realpath(
|
uv.fs_realpath(
|
||||||
os_path,
|
os_path,
|
||||||
|
|
@ -232,11 +231,11 @@ M.normalize_url = function(url, callback)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param url string
|
---@param url string
|
||||||
---@param entry canola.Entry
|
---@param entry oil.Entry
|
||||||
---@param cb fun(path: string)
|
---@param cb fun(path: string)
|
||||||
M.get_entry_path = function(url, entry, cb)
|
M.get_entry_path = function(url, entry, cb)
|
||||||
local internal_entry = assert(cache.get_entry_by_id(entry.id))
|
local internal_entry = assert(cache.get_entry_by_id(entry.id))
|
||||||
local meta = internal_entry[FIELD_META] --[[@as {stat: uv_fs_t, trash_info: canola.WindowsTrashInfo, display_name: string}]]
|
local meta = internal_entry[FIELD_META] --[[@as {stat: uv_fs_t, trash_info: oil.WindowsTrashInfo, display_name: string}]]
|
||||||
local trash_info = meta and meta.trash_info
|
local trash_info = meta and meta.trash_info
|
||||||
if not trash_info then
|
if not trash_info then
|
||||||
-- This is a subpath in the trash
|
-- This is a subpath in the trash
|
||||||
|
|
@ -245,84 +244,84 @@ M.get_entry_path = function(url, entry, cb)
|
||||||
end
|
end
|
||||||
|
|
||||||
local path = fs.os_to_posix_path(trash_info.trash_file)
|
local path = fs.os_to_posix_path(trash_info.trash_file)
|
||||||
if entry.type == 'directory' then
|
if entry.type == "directory" then
|
||||||
path = win_addslash(path)
|
path = win_addslash(path)
|
||||||
end
|
end
|
||||||
cb('canola://' .. path)
|
cb("oil://" .. path)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param err canola.ParseError
|
---@param err oil.ParseError
|
||||||
---@return boolean
|
---@return boolean
|
||||||
M.filter_error = function(err)
|
M.filter_error = function(err)
|
||||||
if err.message == 'Duplicate filename' then
|
if err.message == "Duplicate filename" then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param action canola.Action
|
---@param action oil.Action
|
||||||
---@return string
|
---@return string
|
||||||
M.render_action = function(action)
|
M.render_action = function(action)
|
||||||
if action.type == 'delete' then
|
if action.type == "delete" then
|
||||||
local entry = assert(cache.get_entry_by_url(action.url))
|
local entry = assert(cache.get_entry_by_url(action.url))
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
---@type canola.WindowsTrashInfo
|
---@type oil.WindowsTrashInfo
|
||||||
local trash_info = assert(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
|
||||||
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
||||||
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
||||||
if src_adapter.name == 'files' then
|
if src_adapter.name == "files" then
|
||||||
local _, path = util.parse_url(action.src_url)
|
local _, path = util.parse_url(action.src_url)
|
||||||
assert(path)
|
assert(path)
|
||||||
local short_path = files.to_short_os_path(path, action.entry_type)
|
local short_path = files.to_short_os_path(path, action.entry_type)
|
||||||
return string.format(' TRASH %s', short_path)
|
return string.format(" TRASH %s", short_path)
|
||||||
elseif dest_adapter.name == 'files' then
|
elseif dest_adapter.name == "files" then
|
||||||
local _, path = util.parse_url(action.dest_url)
|
local _, path = util.parse_url(action.dest_url)
|
||||||
assert(path)
|
assert(path)
|
||||||
local short_path = files.to_short_os_path(path, action.entry_type)
|
local short_path = files.to_short_os_path(path, action.entry_type)
|
||||||
return string.format('RESTORE %s', short_path)
|
return string.format("RESTORE %s", short_path)
|
||||||
else
|
else
|
||||||
error('Must be moving files into or out of trash')
|
error("Must be moving files into or out of trash")
|
||||||
end
|
end
|
||||||
elseif action.type == 'copy' then
|
elseif action.type == "copy" then
|
||||||
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
||||||
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
||||||
if src_adapter.name == 'files' then
|
if src_adapter.name == "files" then
|
||||||
local _, path = util.parse_url(action.src_url)
|
local _, path = util.parse_url(action.src_url)
|
||||||
assert(path)
|
assert(path)
|
||||||
local short_path = files.to_short_os_path(path, action.entry_type)
|
local short_path = files.to_short_os_path(path, action.entry_type)
|
||||||
return string.format(' COPY %s -> TRASH', short_path)
|
return string.format(" COPY %s -> TRASH", short_path)
|
||||||
elseif dest_adapter.name == 'files' then
|
elseif dest_adapter.name == "files" then
|
||||||
local _, path = util.parse_url(action.dest_url)
|
local _, path = util.parse_url(action.dest_url)
|
||||||
assert(path)
|
assert(path)
|
||||||
local short_path = files.to_short_os_path(path, action.entry_type)
|
local short_path = files.to_short_os_path(path, action.entry_type)
|
||||||
return string.format('RESTORE %s', short_path)
|
return string.format("RESTORE %s", short_path)
|
||||||
else
|
else
|
||||||
error('Must be copying files into or out of trash')
|
error("Must be copying files into or out of trash")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
error(string.format("Bad action type '%s'", action.type))
|
error(string.format("Bad action type '%s'", action.type))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param trash_info canola.WindowsTrashInfo
|
---@param trash_info oil.WindowsTrashInfo
|
||||||
---@param cb fun(err?: string, raw_entries: canola.WindowsRawEntry[]?)
|
---@param cb fun(err?: string, raw_entries: oil.WindowsRawEntry[]?)
|
||||||
local purge = function(trash_info, cb)
|
local purge = function(trash_info, cb)
|
||||||
fs.recursive_delete('file', trash_info.info_file, function(err)
|
fs.recursive_delete("file", trash_info.info_file, function(err)
|
||||||
if err then
|
if err then
|
||||||
return cb(err)
|
return cb(err)
|
||||||
end
|
end
|
||||||
fs.recursive_delete('file', trash_info.trash_file, cb)
|
fs.recursive_delete("file", trash_info.trash_file, cb)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param path string
|
---@param path string
|
||||||
---@param type string
|
---@param type string
|
||||||
---@param cb fun(err?: string, trash_info?: canola.TrashInfo)
|
---@param cb fun(err?: string, trash_info?: oil.TrashInfo)
|
||||||
local function create_trash_info_and_copy(path, type, cb)
|
local function create_trash_info_and_copy(path, type, cb)
|
||||||
local temp_path = path .. 'temp'
|
local temp_path = path .. "temp"
|
||||||
-- create a temporary copy on the same location
|
-- create a temporary copy on the same location
|
||||||
fs.recursive_copy(
|
fs.recursive_copy(
|
||||||
type,
|
type,
|
||||||
|
|
@ -344,28 +343,28 @@ local function create_trash_info_and_copy(path, type, cb)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param action canola.Action
|
---@param action oil.Action
|
||||||
---@param cb fun(err: nil|string)
|
---@param cb fun(err: nil|string)
|
||||||
M.perform_action = function(action, cb)
|
M.perform_action = function(action, cb)
|
||||||
if action.type == 'delete' then
|
if action.type == "delete" then
|
||||||
local entry = assert(cache.get_entry_by_url(action.url))
|
local entry = assert(cache.get_entry_by_url(action.url))
|
||||||
local meta = entry[FIELD_META] --[[@as {stat: uv_fs_t, trash_info: canola.WindowsTrashInfo, display_name: string}]]
|
local meta = entry[FIELD_META] --[[@as {stat: uv_fs_t, trash_info: oil.WindowsTrashInfo, display_name: string}]]
|
||||||
local trash_info = meta and meta.trash_info
|
local trash_info = meta and meta.trash_info
|
||||||
|
|
||||||
purge(trash_info, cb)
|
purge(trash_info, cb)
|
||||||
elseif action.type == 'move' then
|
elseif action.type == "move" then
|
||||||
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
||||||
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
||||||
if src_adapter.name == 'files' then
|
if src_adapter.name == "files" then
|
||||||
local _, path = util.parse_url(action.src_url)
|
local _, path = util.parse_url(action.src_url)
|
||||||
M.delete_to_trash(assert(path), cb)
|
M.delete_to_trash(assert(path), cb)
|
||||||
elseif dest_adapter.name == 'files' then
|
elseif dest_adapter.name == "files" then
|
||||||
-- Restore
|
-- Restore
|
||||||
local _, dest_path = util.parse_url(action.dest_url)
|
local _, dest_path = util.parse_url(action.dest_url)
|
||||||
assert(dest_path)
|
assert(dest_path)
|
||||||
dest_path = fs.posix_to_os_path(dest_path)
|
dest_path = fs.posix_to_os_path(dest_path)
|
||||||
local entry = assert(cache.get_entry_by_url(action.src_url))
|
local entry = assert(cache.get_entry_by_url(action.src_url))
|
||||||
local meta = entry[FIELD_META] --[[@as {stat: uv_fs_t, trash_info: canola.WindowsTrashInfo, display_name: string}]]
|
local meta = entry[FIELD_META] --[[@as {stat: uv_fs_t, trash_info: oil.WindowsTrashInfo, display_name: string}]]
|
||||||
local trash_info = meta and meta.trash_info
|
local trash_info = meta and meta.trash_info
|
||||||
fs.recursive_move(action.entry_type, trash_info.trash_file, dest_path, function(err)
|
fs.recursive_move(action.entry_type, trash_info.trash_file, dest_path, function(err)
|
||||||
if err then
|
if err then
|
||||||
|
|
@ -374,33 +373,33 @@ M.perform_action = function(action, cb)
|
||||||
uv.fs_unlink(trash_info.info_file, cb)
|
uv.fs_unlink(trash_info.info_file, cb)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
elseif action.type == 'copy' then
|
elseif action.type == "copy" then
|
||||||
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
local src_adapter = assert(config.get_adapter_by_scheme(action.src_url))
|
||||||
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url))
|
||||||
if src_adapter.name == 'files' then
|
if src_adapter.name == "files" then
|
||||||
local _, path = util.parse_url(action.src_url)
|
local _, path = util.parse_url(action.src_url)
|
||||||
assert(path)
|
assert(path)
|
||||||
path = fs.posix_to_os_path(path)
|
path = fs.posix_to_os_path(path)
|
||||||
local entry = assert(cache.get_entry_by_url(action.src_url))
|
local entry = assert(cache.get_entry_by_url(action.src_url))
|
||||||
create_trash_info_and_copy(path, entry[FIELD_TYPE], cb)
|
create_trash_info_and_copy(path, entry[FIELD_TYPE], cb)
|
||||||
elseif dest_adapter.name == 'files' then
|
elseif dest_adapter.name == "files" then
|
||||||
-- Restore
|
-- Restore
|
||||||
local _, dest_path = util.parse_url(action.dest_url)
|
local _, dest_path = util.parse_url(action.dest_url)
|
||||||
assert(dest_path)
|
assert(dest_path)
|
||||||
dest_path = fs.posix_to_os_path(dest_path)
|
dest_path = fs.posix_to_os_path(dest_path)
|
||||||
local entry = assert(cache.get_entry_by_url(action.src_url))
|
local entry = assert(cache.get_entry_by_url(action.src_url))
|
||||||
local meta = entry[FIELD_META] --[[@as {stat: uv_fs_t, trash_info: canola.WindowsTrashInfo, display_name: string}]]
|
local meta = entry[FIELD_META] --[[@as {stat: uv_fs_t, trash_info: oil.WindowsTrashInfo, display_name: string}]]
|
||||||
local trash_info = meta and meta.trash_info
|
local trash_info = meta and meta.trash_info
|
||||||
fs.recursive_copy(action.entry_type, trash_info.trash_file, dest_path, cb)
|
fs.recursive_copy(action.entry_type, trash_info.trash_file, dest_path, cb)
|
||||||
else
|
else
|
||||||
error('Must be moving files into or out of trash')
|
error("Must be moving files into or out of trash")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
cb(string.format('Bad action type: %s', action.type))
|
cb(string.format("Bad action type: %s", action.type))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
M.supported_cross_adapter_actions = { files = 'move' }
|
M.supported_cross_adapter_actions = { files = "move" }
|
||||||
|
|
||||||
---@param path string
|
---@param path string
|
||||||
---@param cb fun(err?: string)
|
---@param cb fun(err?: string)
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
---@class (exact) canola.PowershellCommand
|
---@class (exact) oil.PowershellCommand
|
||||||
---@field cmd string
|
---@field cmd string
|
||||||
---@field cb fun(err?: string, output?: string)
|
---@field cb fun(err?: string, output?: string)
|
||||||
---@field running? boolean
|
---@field running? boolean
|
||||||
|
|
||||||
---@class canola.PowershellConnection
|
---@class oil.PowershellConnection
|
||||||
---@field private jid integer
|
---@field private jid integer
|
||||||
---@field private execution_error? string
|
---@field private execution_error? string
|
||||||
---@field private commands canola.PowershellCommand[]
|
---@field private commands oil.PowershellCommand[]
|
||||||
---@field private stdout string[]
|
---@field private stdout string[]
|
||||||
---@field private is_reading_data boolean
|
---@field private is_reading_data boolean
|
||||||
local PowershellConnection = {}
|
local PowershellConnection = {}
|
||||||
|
|
||||||
---@param init_command? string
|
---@param init_command? string
|
||||||
---@return canola.PowershellConnection
|
---@return oil.PowershellConnection
|
||||||
function PowershellConnection.new(init_command)
|
function PowershellConnection.new(init_command)
|
||||||
local self = setmetatable({
|
local self = setmetatable({
|
||||||
commands = {},
|
commands = {},
|
||||||
|
|
@ -22,7 +22,7 @@ function PowershellConnection.new(init_command)
|
||||||
|
|
||||||
self:_init(init_command)
|
self:_init(init_command)
|
||||||
|
|
||||||
---@type canola.PowershellConnection
|
---@type oil.PowershellConnection
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -41,23 +41,23 @@ function PowershellConnection:_init(init_command)
|
||||||
-- 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({
|
||||||
'cmd',
|
"cmd",
|
||||||
'/c',
|
"/c",
|
||||||
'"chcp 65001 && powershell -NoProfile -NoLogo -ExecutionPolicy Bypass -NoExit -Command -"',
|
'"chcp 65001 && powershell -NoProfile -NoLogo -ExecutionPolicy Bypass -NoExit -Command -"',
|
||||||
}, {
|
}, {
|
||||||
---@param data string[]
|
---@param data string[]
|
||||||
on_stdout = function(_, data)
|
on_stdout = function(_, data)
|
||||||
for _, fragment in ipairs(data) do
|
for _, fragment in ipairs(data) do
|
||||||
if fragment:find('===DONE%((%a+)%)===') then
|
if fragment:find("===DONE%((%a+)%)===") then
|
||||||
self.is_reading_data = false
|
self.is_reading_data = false
|
||||||
local output = table.concat(self.stdout, '')
|
local output = table.concat(self.stdout, "")
|
||||||
local cb = self.commands[1].cb
|
local cb = self.commands[1].cb
|
||||||
table.remove(self.commands, 1)
|
table.remove(self.commands, 1)
|
||||||
local success = fragment:match('===DONE%((%a+)%)===')
|
local success = fragment:match("===DONE%((%a+)%)===")
|
||||||
if success == 'True' then
|
if success == "True" then
|
||||||
cb(nil, output)
|
cb(nil, output)
|
||||||
elseif success == 'False' then
|
elseif success == "False" then
|
||||||
cb(success .. ': ' .. output, output)
|
cb(success .. ": " .. output, output)
|
||||||
end
|
end
|
||||||
self.stdout = {}
|
self.stdout = {}
|
||||||
self:_consume()
|
self:_consume()
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
-- A wrapper around trash operations using windows powershell
|
-- A wrapper around trash operations using windows powershell
|
||||||
local Powershell = require('canola.adapters.trash.windows.powershell-connection')
|
local Powershell = require("oil.adapters.trash.windows.powershell-connection")
|
||||||
|
|
||||||
---@class canola.WindowsRawEntry
|
---@class oil.WindowsRawEntry
|
||||||
---@field IsFolder boolean
|
---@field IsFolder boolean
|
||||||
---@field DeletionDate integer
|
---@field DeletionDate integer
|
||||||
---@field Name string
|
---@field Name string
|
||||||
|
|
@ -30,10 +30,10 @@ $data = @(foreach ($i in $folder.items())
|
||||||
ConvertTo-Json $data -Compress
|
ConvertTo-Json $data -Compress
|
||||||
]]
|
]]
|
||||||
|
|
||||||
---@type nil|canola.PowershellConnection
|
---@type nil|oil.PowershellConnection
|
||||||
local list_entries_powershell
|
local list_entries_powershell
|
||||||
|
|
||||||
---@param cb fun(err?: string, raw_entries?: canola.WindowsRawEntry[])
|
---@param cb fun(err?: string, raw_entries?: oil.WindowsRawEntry[])
|
||||||
M.list_raw_entries = function(cb)
|
M.list_raw_entries = function(cb)
|
||||||
if not list_entries_powershell then
|
if not list_entries_powershell then
|
||||||
list_entries_powershell = Powershell.new(list_entries_init)
|
list_entries_powershell = Powershell.new(list_entries_init)
|
||||||
|
|
@ -63,7 +63,7 @@ $path = Get-Item '%s'
|
||||||
$folder.ParseName($path.FullName).InvokeVerb('delete')
|
$folder.ParseName($path.FullName).InvokeVerb('delete')
|
||||||
]]
|
]]
|
||||||
|
|
||||||
---@type nil|canola.PowershellConnection
|
---@type nil|oil.PowershellConnection
|
||||||
local delete_to_trash_powershell
|
local delete_to_trash_powershell
|
||||||
|
|
||||||
---@param path string
|
---@param path string
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
local constants = require('canola.constants')
|
local constants = require("oil.constants")
|
||||||
local util = require('canola.util')
|
local util = require("oil.util")
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local FIELD_ID = constants.FIELD_ID
|
local FIELD_ID = constants.FIELD_ID
|
||||||
|
|
@ -8,11 +8,11 @@ local FIELD_META = constants.FIELD_META
|
||||||
|
|
||||||
local next_id = 1
|
local next_id = 1
|
||||||
|
|
||||||
-- Map<url, Map<entry name, canola.InternalEntry>>
|
-- Map<url, Map<entry name, oil.InternalEntry>>
|
||||||
---@type table<string, table<string, canola.InternalEntry>>
|
---@type table<string, table<string, oil.InternalEntry>>
|
||||||
local url_directory = {}
|
local url_directory = {}
|
||||||
|
|
||||||
---@type table<integer, canola.InternalEntry>
|
---@type table<integer, oil.InternalEntry>
|
||||||
local entries_by_id = {}
|
local entries_by_id = {}
|
||||||
|
|
||||||
---@type table<integer, string>
|
---@type table<integer, string>
|
||||||
|
|
@ -28,7 +28,7 @@ local _cached_id_fmt
|
||||||
M.format_id = function(id)
|
M.format_id = function(id)
|
||||||
if not _cached_id_fmt then
|
if not _cached_id_fmt then
|
||||||
local id_str_length = math.max(3, 1 + math.floor(math.log10(next_id)))
|
local id_str_length = math.max(3, 1 + math.floor(math.log10(next_id)))
|
||||||
_cached_id_fmt = '/%0' .. string.format('%d', id_str_length) .. 'd'
|
_cached_id_fmt = "/%0" .. string.format("%d", id_str_length) .. "d"
|
||||||
end
|
end
|
||||||
return _cached_id_fmt:format(id)
|
return _cached_id_fmt:format(id)
|
||||||
end
|
end
|
||||||
|
|
@ -42,8 +42,8 @@ end
|
||||||
|
|
||||||
---@param parent_url string
|
---@param parent_url string
|
||||||
---@param name string
|
---@param name string
|
||||||
---@param type canola.EntryType
|
---@param type oil.EntryType
|
||||||
---@return canola.InternalEntry
|
---@return oil.InternalEntry
|
||||||
M.create_entry = function(parent_url, name, type)
|
M.create_entry = function(parent_url, name, type)
|
||||||
parent_url = util.addslash(parent_url)
|
parent_url = util.addslash(parent_url)
|
||||||
local parent = tmp_url_directory[parent_url] or url_directory[parent_url]
|
local parent = tmp_url_directory[parent_url] or url_directory[parent_url]
|
||||||
|
|
@ -58,7 +58,7 @@ M.create_entry = function(parent_url, name, type)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param parent_url string
|
---@param parent_url string
|
||||||
---@param entry canola.InternalEntry
|
---@param entry oil.InternalEntry
|
||||||
M.store_entry = function(parent_url, entry)
|
M.store_entry = function(parent_url, entry)
|
||||||
parent_url = util.addslash(parent_url)
|
parent_url = util.addslash(parent_url)
|
||||||
local parent = url_directory[parent_url]
|
local parent = url_directory[parent_url]
|
||||||
|
|
@ -85,8 +85,8 @@ end
|
||||||
|
|
||||||
---@param parent_url string
|
---@param parent_url string
|
||||||
---@param name string
|
---@param name string
|
||||||
---@param type canola.EntryType
|
---@param type oil.EntryType
|
||||||
---@return canola.InternalEntry
|
---@return oil.InternalEntry
|
||||||
M.create_and_store_entry = function(parent_url, name, type)
|
M.create_and_store_entry = function(parent_url, name, type)
|
||||||
local entry = M.create_entry(parent_url, name, type)
|
local entry = M.create_entry(parent_url, name, type)
|
||||||
M.store_entry(parent_url, entry)
|
M.store_entry(parent_url, entry)
|
||||||
|
|
@ -115,18 +115,18 @@ M.end_update_url = function(parent_url)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param id integer
|
---@param id integer
|
||||||
---@return nil|canola.InternalEntry
|
---@return nil|oil.InternalEntry
|
||||||
M.get_entry_by_id = function(id)
|
M.get_entry_by_id = function(id)
|
||||||
return entries_by_id[id]
|
return entries_by_id[id]
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param url string
|
---@param url string
|
||||||
---@return nil|canola.InternalEntry
|
---@return nil|oil.InternalEntry
|
||||||
M.get_entry_by_url = function(url)
|
M.get_entry_by_url = function(url)
|
||||||
local scheme, path = util.parse_url(url)
|
local scheme, path = util.parse_url(url)
|
||||||
assert(path)
|
assert(path)
|
||||||
local parent_url = scheme .. vim.fn.fnamemodify(path, ':h')
|
local parent_url = scheme .. vim.fn.fnamemodify(path, ":h")
|
||||||
local basename = vim.fn.fnamemodify(path, ':t')
|
local basename = vim.fn.fnamemodify(path, ":t")
|
||||||
return M.list_url(parent_url)[basename]
|
return M.list_url(parent_url)[basename]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -135,46 +135,46 @@ end
|
||||||
M.get_parent_url = function(id)
|
M.get_parent_url = function(id)
|
||||||
local url = parent_url_by_id[id]
|
local url = parent_url_by_id[id]
|
||||||
if not url then
|
if not url then
|
||||||
error(string.format('Entry %d missing parent url', id))
|
error(string.format("Entry %d missing parent url", id))
|
||||||
end
|
end
|
||||||
return url
|
return url
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param url string
|
---@param url string
|
||||||
---@return table<string, canola.InternalEntry>
|
---@return table<string, oil.InternalEntry>
|
||||||
M.list_url = function(url)
|
M.list_url = function(url)
|
||||||
url = util.addslash(url)
|
url = util.addslash(url)
|
||||||
return url_directory[url] or {}
|
return url_directory[url] or {}
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param action canola.Action
|
---@param action oil.Action
|
||||||
M.perform_action = function(action)
|
M.perform_action = function(action)
|
||||||
if action.type == 'create' then
|
if action.type == "create" then
|
||||||
local scheme, path = util.parse_url(action.url)
|
local scheme, path = util.parse_url(action.url)
|
||||||
assert(path)
|
assert(path)
|
||||||
local parent_url = util.addslash(scheme .. vim.fn.fnamemodify(path, ':h'))
|
local parent_url = util.addslash(scheme .. vim.fn.fnamemodify(path, ":h"))
|
||||||
local name = vim.fn.fnamemodify(path, ':t')
|
local name = vim.fn.fnamemodify(path, ":t")
|
||||||
M.create_and_store_entry(parent_url, name, action.entry_type)
|
M.create_and_store_entry(parent_url, name, action.entry_type)
|
||||||
elseif action.type == 'delete' then
|
elseif action.type == "delete" then
|
||||||
local scheme, path = util.parse_url(action.url)
|
local scheme, path = util.parse_url(action.url)
|
||||||
assert(path)
|
assert(path)
|
||||||
local parent_url = util.addslash(scheme .. vim.fn.fnamemodify(path, ':h'))
|
local parent_url = util.addslash(scheme .. vim.fn.fnamemodify(path, ":h"))
|
||||||
local name = vim.fn.fnamemodify(path, ':t')
|
local name = vim.fn.fnamemodify(path, ":t")
|
||||||
local entry = url_directory[parent_url][name]
|
local entry = url_directory[parent_url][name]
|
||||||
url_directory[parent_url][name] = nil
|
url_directory[parent_url][name] = nil
|
||||||
entries_by_id[entry[FIELD_ID]] = nil
|
entries_by_id[entry[FIELD_ID]] = nil
|
||||||
parent_url_by_id[entry[FIELD_ID]] = nil
|
parent_url_by_id[entry[FIELD_ID]] = nil
|
||||||
elseif action.type == 'move' then
|
elseif action.type == "move" then
|
||||||
local src_scheme, src_path = util.parse_url(action.src_url)
|
local src_scheme, src_path = util.parse_url(action.src_url)
|
||||||
assert(src_path)
|
assert(src_path)
|
||||||
local src_parent_url = util.addslash(src_scheme .. vim.fn.fnamemodify(src_path, ':h'))
|
local src_parent_url = util.addslash(src_scheme .. vim.fn.fnamemodify(src_path, ":h"))
|
||||||
local src_name = vim.fn.fnamemodify(src_path, ':t')
|
local src_name = vim.fn.fnamemodify(src_path, ":t")
|
||||||
local entry = url_directory[src_parent_url][src_name]
|
local entry = url_directory[src_parent_url][src_name]
|
||||||
|
|
||||||
local dest_scheme, dest_path = util.parse_url(action.dest_url)
|
local dest_scheme, dest_path = util.parse_url(action.dest_url)
|
||||||
assert(dest_path)
|
assert(dest_path)
|
||||||
local dest_parent_url = util.addslash(dest_scheme .. vim.fn.fnamemodify(dest_path, ':h'))
|
local dest_parent_url = util.addslash(dest_scheme .. vim.fn.fnamemodify(dest_path, ":h"))
|
||||||
local dest_name = vim.fn.fnamemodify(dest_path, ':t')
|
local dest_name = vim.fn.fnamemodify(dest_path, ":t")
|
||||||
|
|
||||||
url_directory[src_parent_url][src_name] = nil
|
url_directory[src_parent_url][src_name] = nil
|
||||||
local dest_parent = url_directory[dest_parent_url]
|
local dest_parent = url_directory[dest_parent_url]
|
||||||
|
|
@ -188,14 +188,13 @@ M.perform_action = function(action)
|
||||||
parent_url_by_id[entry[FIELD_ID]] = dest_parent_url
|
parent_url_by_id[entry[FIELD_ID]] = dest_parent_url
|
||||||
entry[FIELD_NAME] = dest_name
|
entry[FIELD_NAME] = dest_name
|
||||||
util.update_moved_buffers(action.entry_type, action.src_url, action.dest_url)
|
util.update_moved_buffers(action.entry_type, action.src_url, action.dest_url)
|
||||||
elseif action.type == 'copy' then
|
elseif action.type == "copy" then
|
||||||
local scheme, path = util.parse_url(action.dest_url)
|
local scheme, path = util.parse_url(action.dest_url)
|
||||||
assert(path)
|
assert(path)
|
||||||
local parent_url = util.addslash(scheme .. vim.fn.fnamemodify(path, ':h'))
|
local parent_url = util.addslash(scheme .. vim.fn.fnamemodify(path, ":h"))
|
||||||
local name = vim.fn.fnamemodify(path, ':t')
|
local name = vim.fn.fnamemodify(path, ":t")
|
||||||
M.create_and_store_entry(parent_url, name, action.entry_type)
|
M.create_and_store_entry(parent_url, name, action.entry_type)
|
||||||
-- selene: allow(empty_if)
|
elseif action.type == "change" then
|
||||||
elseif action.type == 'change' then
|
|
||||||
-- Cache doesn't need to update
|
-- Cache doesn't need to update
|
||||||
else
|
else
|
||||||
---@diagnostic disable-next-line: undefined-field
|
---@diagnostic disable-next-line: undefined-field
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
local cache = require('canola.cache')
|
local cache = require("oil.cache")
|
||||||
local canola = require('canola')
|
local columns = require("oil.columns")
|
||||||
local columns = require('canola.columns')
|
local config = require("oil.config")
|
||||||
local config = require('canola.config')
|
local fs = require("oil.fs")
|
||||||
local fs = require('canola.fs')
|
local oil = require("oil")
|
||||||
local parser = require('canola.mutator.parser')
|
local parser = require("oil.mutator.parser")
|
||||||
local util = require('canola.util')
|
local util = require("oil.util")
|
||||||
local view = require('canola.view')
|
local view = require("oil.view")
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
|
|
@ -16,10 +16,10 @@ local function get_linux_session_type()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
xdg_session_type = xdg_session_type:lower()
|
xdg_session_type = xdg_session_type:lower()
|
||||||
if xdg_session_type:find('x11') then
|
if xdg_session_type:find("x11") then
|
||||||
return 'x11'
|
return "x11"
|
||||||
elseif xdg_session_type:find('wayland') then
|
elseif xdg_session_type:find("wayland") then
|
||||||
return 'wayland'
|
return "wayland"
|
||||||
else
|
else
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
@ -29,15 +29,15 @@ end
|
||||||
local function is_linux_desktop_gnome()
|
local function is_linux_desktop_gnome()
|
||||||
local cur_desktop = vim.env.XDG_CURRENT_DESKTOP
|
local cur_desktop = vim.env.XDG_CURRENT_DESKTOP
|
||||||
local session_desktop = vim.env.XDG_SESSION_DESKTOP
|
local session_desktop = vim.env.XDG_SESSION_DESKTOP
|
||||||
local idx = session_desktop and session_desktop:lower():find('gnome')
|
local idx = session_desktop and session_desktop:lower():find("gnome")
|
||||||
or cur_desktop and cur_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'
|
return idx ~= nil or cur_desktop == "X-Cinnamon" or cur_desktop == "XFCE"
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param winid integer
|
---@param winid integer
|
||||||
---@param entry canola.InternalEntry
|
---@param entry oil.InternalEntry
|
||||||
---@param column_defs canola.ColumnSpec[]
|
---@param column_defs oil.ColumnSpec[]
|
||||||
---@param adapter canola.Adapter
|
---@param adapter oil.Adapter
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
local function write_pasted(winid, entry, column_defs, adapter, bufnr)
|
local function write_pasted(winid, entry, column_defs, adapter, bufnr)
|
||||||
local col_width = {}
|
local col_width = {}
|
||||||
|
|
@ -52,10 +52,10 @@ local function write_pasted(winid, entry, column_defs, adapter, bufnr)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param parent_url string
|
---@param parent_url string
|
||||||
---@param entry canola.InternalEntry
|
---@param entry oil.InternalEntry
|
||||||
local function remove_entry_from_parent_buffer(parent_url, entry)
|
local function remove_entry_from_parent_buffer(parent_url, entry)
|
||||||
local bufnr = vim.fn.bufadd(parent_url)
|
local bufnr = vim.fn.bufadd(parent_url)
|
||||||
assert(vim.api.nvim_buf_is_loaded(bufnr), 'Expected parent buffer to be loaded during paste')
|
assert(vim.api.nvim_buf_is_loaded(bufnr), "Expected parent buffer to be loaded during paste")
|
||||||
local adapter = assert(util.get_adapter(bufnr))
|
local adapter = assert(util.get_adapter(bufnr))
|
||||||
local column_defs = columns.get_supported_columns(adapter)
|
local column_defs = columns.get_supported_columns(adapter)
|
||||||
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
||||||
|
|
@ -77,7 +77,7 @@ end
|
||||||
---@param delete_original? boolean
|
---@param delete_original? boolean
|
||||||
local function paste_paths(paths, delete_original)
|
local function paste_paths(paths, delete_original)
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
local bufnr = vim.api.nvim_get_current_buf()
|
||||||
local scheme = 'canola://'
|
local scheme = "oil://"
|
||||||
local adapter = assert(config.get_adapter_by_scheme(scheme))
|
local adapter = assert(config.get_adapter_by_scheme(scheme))
|
||||||
local column_defs = columns.get_supported_columns(scheme)
|
local column_defs = columns.get_supported_columns(scheme)
|
||||||
local winid = vim.api.nvim_get_current_win()
|
local winid = vim.api.nvim_get_current_win()
|
||||||
|
|
@ -88,7 +88,7 @@ local function paste_paths(paths, delete_original)
|
||||||
-- Handle as many paths synchronously as possible
|
-- Handle as many paths synchronously as possible
|
||||||
for _, path in ipairs(paths) do
|
for _, path in ipairs(paths) do
|
||||||
-- Trim the trailing slash off directories
|
-- Trim the trailing slash off directories
|
||||||
if vim.endswith(path, '/') then
|
if vim.endswith(path, "/") then
|
||||||
path = path:sub(1, -2)
|
path = path:sub(1, -2)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -114,7 +114,7 @@ local function paste_paths(paths, delete_original)
|
||||||
local cursor = vim.api.nvim_win_get_cursor(winid)
|
local cursor = vim.api.nvim_win_get_cursor(winid)
|
||||||
local complete_loading = util.cb_collect(#vim.tbl_keys(parent_urls), function(err)
|
local complete_loading = util.cb_collect(#vim.tbl_keys(parent_urls), function(err)
|
||||||
if err then
|
if err then
|
||||||
vim.notify(string.format('Error loading parent directory: %s', err), vim.log.levels.ERROR)
|
vim.notify(string.format("Error loading parent directory: %s", err), vim.log.levels.ERROR)
|
||||||
else
|
else
|
||||||
-- Something in this process moves the cursor to the top of the window, so have to restore it
|
-- 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)
|
vim.api.nvim_win_set_cursor(winid, cursor)
|
||||||
|
|
@ -140,7 +140,7 @@ local function paste_paths(paths, delete_original)
|
||||||
for parent_url, _ in pairs(parent_urls) do
|
for parent_url, _ in pairs(parent_urls) do
|
||||||
local new_bufnr = vim.api.nvim_create_buf(false, false)
|
local new_bufnr = vim.api.nvim_create_buf(false, false)
|
||||||
vim.api.nvim_buf_set_name(new_bufnr, parent_url)
|
vim.api.nvim_buf_set_name(new_bufnr, parent_url)
|
||||||
canola.load_canola_buffer(new_bufnr)
|
oil.load_oil_buffer(new_bufnr)
|
||||||
util.run_after_load(new_bufnr, complete_loading)
|
util.run_after_load(new_bufnr, complete_loading)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -149,8 +149,8 @@ end
|
||||||
---@return integer end
|
---@return integer end
|
||||||
local function range_from_selection()
|
local function range_from_selection()
|
||||||
-- [bufnum, lnum, col, off]; both row and column 1-indexed
|
-- [bufnum, lnum, col, off]; both row and column 1-indexed
|
||||||
local start = vim.fn.getpos('v')
|
local start = vim.fn.getpos("v")
|
||||||
local end_ = vim.fn.getpos('.')
|
local end_ = vim.fn.getpos(".")
|
||||||
local start_row = start[2]
|
local start_row = start[2]
|
||||||
local end_row = end_[2]
|
local end_row = end_[2]
|
||||||
|
|
||||||
|
|
@ -162,38 +162,38 @@ local function range_from_selection()
|
||||||
end
|
end
|
||||||
|
|
||||||
M.copy_to_system_clipboard = function()
|
M.copy_to_system_clipboard = function()
|
||||||
local dir = canola.get_current_dir()
|
local dir = oil.get_current_dir()
|
||||||
if not dir then
|
if not dir then
|
||||||
vim.notify('System clipboard only works for local files', vim.log.levels.ERROR)
|
vim.notify("System clipboard only works for local files", vim.log.levels.ERROR)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local entries = {}
|
local entries = {}
|
||||||
local mode = vim.api.nvim_get_mode().mode
|
local mode = vim.api.nvim_get_mode().mode
|
||||||
if mode == 'v' or mode == 'V' then
|
if mode == "v" or mode == "V" then
|
||||||
if fs.is_mac then
|
if fs.is_mac then
|
||||||
vim.notify(
|
vim.notify(
|
||||||
'Copying multiple paths to clipboard is not supported on mac',
|
"Copying multiple paths to clipboard is not supported on mac",
|
||||||
vim.log.levels.ERROR
|
vim.log.levels.ERROR
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local start_row, end_row = range_from_selection()
|
local start_row, end_row = range_from_selection()
|
||||||
for i = start_row, end_row do
|
for i = start_row, end_row do
|
||||||
table.insert(entries, canola.get_entry_on_line(0, i))
|
table.insert(entries, oil.get_entry_on_line(0, i))
|
||||||
end
|
end
|
||||||
|
|
||||||
-- leave visual mode
|
-- leave visual mode
|
||||||
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes('<Esc>', true, false, true), 'n', true)
|
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("<Esc>", true, false, true), "n", true)
|
||||||
else
|
else
|
||||||
table.insert(entries, canola.get_cursor_entry())
|
table.insert(entries, oil.get_cursor_entry())
|
||||||
end
|
end
|
||||||
|
|
||||||
-- This removes holes in the list-like table
|
-- This removes holes in the list-like table
|
||||||
entries = vim.tbl_values(entries)
|
entries = vim.tbl_values(entries)
|
||||||
|
|
||||||
if #entries == 0 then
|
if #entries == 0 then
|
||||||
vim.notify('Could not find local file under cursor', vim.log.levels.WARN)
|
vim.notify("Could not find local file under cursor", vim.log.levels.WARN)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local paths = {}
|
local paths = {}
|
||||||
|
|
@ -204,38 +204,38 @@ M.copy_to_system_clipboard = function()
|
||||||
local stdin
|
local stdin
|
||||||
if fs.is_mac then
|
if fs.is_mac then
|
||||||
cmd = {
|
cmd = {
|
||||||
'osascript',
|
"osascript",
|
||||||
'-e',
|
"-e",
|
||||||
'on run args',
|
"on run args",
|
||||||
'-e',
|
"-e",
|
||||||
'set the clipboard to POSIX file (first item of args)',
|
"set the clipboard to POSIX file (first item of args)",
|
||||||
'-e',
|
"-e",
|
||||||
'end run',
|
"end run",
|
||||||
paths[1],
|
paths[1],
|
||||||
}
|
}
|
||||||
elseif fs.is_linux then
|
elseif fs.is_linux then
|
||||||
local xdg_session_type = get_linux_session_type()
|
local xdg_session_type = get_linux_session_type()
|
||||||
if xdg_session_type == 'x11' then
|
if xdg_session_type == "x11" then
|
||||||
vim.list_extend(cmd, { 'xclip', '-i', '-selection', 'clipboard' })
|
vim.list_extend(cmd, { "xclip", "-i", "-selection", "clipboard" })
|
||||||
elseif xdg_session_type == 'wayland' then
|
elseif xdg_session_type == "wayland" then
|
||||||
table.insert(cmd, 'wl-copy')
|
table.insert(cmd, "wl-copy")
|
||||||
else
|
else
|
||||||
vim.notify('System clipboard not supported, check $XDG_SESSION_TYPE', vim.log.levels.ERROR)
|
vim.notify("System clipboard not supported, check $XDG_SESSION_TYPE", vim.log.levels.ERROR)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local urls = {}
|
local urls = {}
|
||||||
for _, path in ipairs(paths) do
|
for _, path in ipairs(paths) do
|
||||||
table.insert(urls, 'file://' .. path)
|
table.insert(urls, "file://" .. path)
|
||||||
end
|
end
|
||||||
if is_linux_desktop_gnome() then
|
if is_linux_desktop_gnome() then
|
||||||
stdin = string.format('copy\n%s\0', table.concat(urls, '\n'))
|
stdin = string.format("copy\n%s\0", table.concat(urls, "\n"))
|
||||||
vim.list_extend(cmd, { '-t', 'x-special/gnome-copied-files' })
|
vim.list_extend(cmd, { "-t", "x-special/gnome-copied-files" })
|
||||||
else
|
else
|
||||||
stdin = table.concat(urls, '\n') .. '\n'
|
stdin = table.concat(urls, "\n") .. "\n"
|
||||||
vim.list_extend(cmd, { '-t', 'text/uri-list' })
|
vim.list_extend(cmd, { "-t", "text/uri-list" })
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
vim.notify('System clipboard not supported on Windows', vim.log.levels.ERROR)
|
vim.notify("System clipboard not supported on Windows", vim.log.levels.ERROR)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -243,11 +243,11 @@ M.copy_to_system_clipboard = function()
|
||||||
vim.notify(string.format("Could not find executable '%s'", cmd[1]), vim.log.levels.ERROR)
|
vim.notify(string.format("Could not find executable '%s'", cmd[1]), vim.log.levels.ERROR)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local stderr = ''
|
local stderr = ""
|
||||||
local jid = vim.fn.jobstart(cmd, {
|
local jid = vim.fn.jobstart(cmd, {
|
||||||
stderr_buffered = true,
|
stderr_buffered = true,
|
||||||
on_stderr = function(_, data)
|
on_stderr = function(_, data)
|
||||||
stderr = table.concat(data, '\n')
|
stderr = table.concat(data, "\n")
|
||||||
end,
|
end,
|
||||||
on_exit = function(j, exit_code)
|
on_exit = function(j, exit_code)
|
||||||
if exit_code ~= 0 then
|
if exit_code ~= 0 then
|
||||||
|
|
@ -259,15 +259,15 @@ M.copy_to_system_clipboard = function()
|
||||||
if #paths == 1 then
|
if #paths == 1 then
|
||||||
vim.notify(string.format("Copied '%s' to system clipboard", paths[1]))
|
vim.notify(string.format("Copied '%s' to system clipboard", paths[1]))
|
||||||
else
|
else
|
||||||
vim.notify(string.format('Copied %d files to system clipboard', #paths))
|
vim.notify(string.format("Copied %d files to system clipboard", #paths))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
assert(jid > 0, 'Failed to start job')
|
assert(jid > 0, "Failed to start job")
|
||||||
if stdin then
|
if stdin then
|
||||||
vim.api.nvim_chan_send(jid, stdin)
|
vim.api.nvim_chan_send(jid, stdin)
|
||||||
vim.fn.chanclose(jid, 'stdin')
|
vim.fn.chanclose(jid, "stdin")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -276,7 +276,7 @@ end
|
||||||
local function handle_paste_output_mac(lines)
|
local function handle_paste_output_mac(lines)
|
||||||
local ret = {}
|
local ret = {}
|
||||||
for _, line in ipairs(lines) do
|
for _, line in ipairs(lines) do
|
||||||
if not line:match('^%s*$') then
|
if not line:match("^%s*$") then
|
||||||
table.insert(ret, line)
|
table.insert(ret, line)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -288,7 +288,7 @@ end
|
||||||
local function handle_paste_output_linux(lines)
|
local function handle_paste_output_linux(lines)
|
||||||
local ret = {}
|
local ret = {}
|
||||||
for _, line in ipairs(lines) do
|
for _, line in ipairs(lines) do
|
||||||
local path = line:match('^file://(.+)$')
|
local path = line:match("^file://(.+)$")
|
||||||
if path then
|
if path then
|
||||||
table.insert(ret, util.url_unescape(path))
|
table.insert(ret, util.url_unescape(path))
|
||||||
end
|
end
|
||||||
|
|
@ -298,7 +298,7 @@ end
|
||||||
|
|
||||||
---@param delete_original? boolean Delete the source file after pasting
|
---@param delete_original? boolean Delete the source file after pasting
|
||||||
M.paste_from_system_clipboard = function(delete_original)
|
M.paste_from_system_clipboard = function(delete_original)
|
||||||
local dir = canola.get_current_dir()
|
local dir = oil.get_current_dir()
|
||||||
if not dir then
|
if not dir then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
@ -306,37 +306,37 @@ M.paste_from_system_clipboard = function(delete_original)
|
||||||
local handle_paste_output
|
local handle_paste_output
|
||||||
if fs.is_mac then
|
if fs.is_mac then
|
||||||
cmd = {
|
cmd = {
|
||||||
'osascript',
|
"osascript",
|
||||||
'-e',
|
"-e",
|
||||||
'on run',
|
"on run",
|
||||||
'-e',
|
"-e",
|
||||||
'POSIX path of (the clipboard as «class furl»)',
|
"POSIX path of (the clipboard as «class furl»)",
|
||||||
'-e',
|
"-e",
|
||||||
'end run',
|
"end run",
|
||||||
}
|
}
|
||||||
handle_paste_output = handle_paste_output_mac
|
handle_paste_output = handle_paste_output_mac
|
||||||
elseif fs.is_linux then
|
elseif fs.is_linux then
|
||||||
local xdg_session_type = get_linux_session_type()
|
local xdg_session_type = get_linux_session_type()
|
||||||
if xdg_session_type == 'x11' then
|
if xdg_session_type == "x11" then
|
||||||
vim.list_extend(cmd, { 'xclip', '-o', '-selection', 'clipboard' })
|
vim.list_extend(cmd, { "xclip", "-o", "-selection", "clipboard" })
|
||||||
elseif xdg_session_type == 'wayland' then
|
elseif xdg_session_type == "wayland" then
|
||||||
table.insert(cmd, 'wl-paste')
|
table.insert(cmd, "wl-paste")
|
||||||
else
|
else
|
||||||
vim.notify('System clipboard not supported, check $XDG_SESSION_TYPE', vim.log.levels.ERROR)
|
vim.notify("System clipboard not supported, check $XDG_SESSION_TYPE", vim.log.levels.ERROR)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
if is_linux_desktop_gnome() then
|
if is_linux_desktop_gnome() then
|
||||||
vim.list_extend(cmd, { '-t', 'x-special/gnome-copied-files' })
|
vim.list_extend(cmd, { "-t", "x-special/gnome-copied-files" })
|
||||||
else
|
else
|
||||||
vim.list_extend(cmd, { '-t', 'text/uri-list' })
|
vim.list_extend(cmd, { "-t", "text/uri-list" })
|
||||||
end
|
end
|
||||||
handle_paste_output = handle_paste_output_linux
|
handle_paste_output = handle_paste_output_linux
|
||||||
else
|
else
|
||||||
vim.notify('System clipboard not supported on Windows', vim.log.levels.ERROR)
|
vim.notify("System clipboard not supported on Windows", vim.log.levels.ERROR)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local paths
|
local paths
|
||||||
local stderr = ''
|
local stderr = ""
|
||||||
if vim.fn.executable(cmd[1]) == 0 then
|
if vim.fn.executable(cmd[1]) == 0 then
|
||||||
vim.notify(string.format("Could not find executable '%s'", cmd[1]), vim.log.levels.ERROR)
|
vim.notify(string.format("Could not find executable '%s'", cmd[1]), vim.log.levels.ERROR)
|
||||||
return
|
return
|
||||||
|
|
@ -345,26 +345,26 @@ M.paste_from_system_clipboard = function(delete_original)
|
||||||
stdout_buffered = true,
|
stdout_buffered = true,
|
||||||
stderr_buffered = true,
|
stderr_buffered = true,
|
||||||
on_stdout = function(j, data)
|
on_stdout = function(j, data)
|
||||||
local lines = vim.split(table.concat(data, '\n'), '\r?\n')
|
local lines = vim.split(table.concat(data, "\n"), "\r?\n")
|
||||||
paths = handle_paste_output(lines)
|
paths = handle_paste_output(lines)
|
||||||
end,
|
end,
|
||||||
on_stderr = function(_, data)
|
on_stderr = function(_, data)
|
||||||
stderr = table.concat(data, '\n')
|
stderr = table.concat(data, "\n")
|
||||||
end,
|
end,
|
||||||
on_exit = function(j, exit_code)
|
on_exit = function(j, exit_code)
|
||||||
if exit_code ~= 0 or not paths then
|
if exit_code ~= 0 or not paths then
|
||||||
vim.notify(
|
vim.notify(
|
||||||
string.format('Error pasting from system clipboard: %s', stderr),
|
string.format("Error pasting from system clipboard: %s", stderr),
|
||||||
vim.log.levels.ERROR
|
vim.log.levels.ERROR
|
||||||
)
|
)
|
||||||
elseif #paths == 0 then
|
elseif #paths == 0 then
|
||||||
vim.notify('No valid files found in system clipboard', vim.log.levels.WARN)
|
vim.notify("No valid files found in system clipboard", vim.log.levels.WARN)
|
||||||
else
|
else
|
||||||
paste_paths(paths, delete_original)
|
paste_paths(paths, delete_original)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
assert(jid > 0, 'Failed to start job')
|
assert(jid > 0, "Failed to start job")
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
local config = require('canola.config')
|
local config = require("oil.config")
|
||||||
local constants = require('canola.constants')
|
local constants = require("oil.constants")
|
||||||
local util = require('canola.util')
|
local util = require("oil.util")
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local FIELD_NAME = constants.FIELD_NAME
|
local FIELD_NAME = constants.FIELD_NAME
|
||||||
|
|
@ -9,36 +9,36 @@ local FIELD_META = constants.FIELD_META
|
||||||
|
|
||||||
local all_columns = {}
|
local all_columns = {}
|
||||||
|
|
||||||
---@alias canola.ColumnSpec string|{[1]: string, [string]: any}
|
---@alias oil.ColumnSpec string|{[1]: string, [string]: any}
|
||||||
|
|
||||||
---@class (exact) canola.ColumnDefinition
|
---@class (exact) oil.ColumnDefinition
|
||||||
---@field render fun(entry: canola.InternalEntry, conf: nil|table, bufnr: integer): nil|canola.TextChunk
|
---@field render fun(entry: oil.InternalEntry, conf: nil|table, bufnr: integer): nil|oil.TextChunk
|
||||||
---@field parse fun(line: string, conf: nil|table): nil|string, nil|string
|
---@field parse fun(line: string, conf: nil|table): nil|string, nil|string
|
||||||
---@field compare? fun(entry: canola.InternalEntry, parsed_value: any): boolean
|
---@field compare? fun(entry: oil.InternalEntry, parsed_value: any): boolean
|
||||||
---@field render_action? fun(action: canola.ChangeAction): string
|
---@field render_action? fun(action: oil.ChangeAction): string
|
||||||
---@field perform_action? fun(action: canola.ChangeAction, callback: fun(err: nil|string))
|
---@field perform_action? fun(action: oil.ChangeAction, callback: fun(err: nil|string))
|
||||||
---@field get_sort_value? fun(entry: canola.InternalEntry): number|string
|
---@field get_sort_value? fun(entry: oil.InternalEntry): number|string
|
||||||
---@field create_sort_value_factory? fun(num_entries: integer): fun(entry: canola.InternalEntry): number|string
|
---@field create_sort_value_factory? fun(num_entries: integer): fun(entry: oil.InternalEntry): number|string
|
||||||
|
|
||||||
---@param name string
|
---@param name string
|
||||||
---@param column canola.ColumnDefinition
|
---@param column oil.ColumnDefinition
|
||||||
M.register = function(name, column)
|
M.register = function(name, column)
|
||||||
all_columns[name] = column
|
all_columns[name] = column
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param adapter canola.Adapter
|
---@param adapter oil.Adapter
|
||||||
---@param defn canola.ColumnSpec
|
---@param defn oil.ColumnSpec
|
||||||
---@return nil|canola.ColumnDefinition
|
---@return nil|oil.ColumnDefinition
|
||||||
M.get_column = function(adapter, defn)
|
M.get_column = function(adapter, defn)
|
||||||
local name = util.split_config(defn)
|
local name = util.split_config(defn)
|
||||||
return all_columns[name] or adapter.get_column(name)
|
return all_columns[name] or adapter.get_column(name)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param adapter_or_scheme string|canola.Adapter
|
---@param adapter_or_scheme string|oil.Adapter
|
||||||
---@return canola.ColumnSpec[]
|
---@return oil.ColumnSpec[]
|
||||||
M.get_supported_columns = function(adapter_or_scheme)
|
M.get_supported_columns = function(adapter_or_scheme)
|
||||||
local adapter
|
local adapter
|
||||||
if type(adapter_or_scheme) == 'string' then
|
if type(adapter_or_scheme) == "string" then
|
||||||
adapter = config.get_adapter_by_scheme(adapter_or_scheme)
|
adapter = config.get_adapter_by_scheme(adapter_or_scheme)
|
||||||
else
|
else
|
||||||
adapter = adapter_or_scheme
|
adapter = adapter_or_scheme
|
||||||
|
|
@ -53,15 +53,15 @@ M.get_supported_columns = function(adapter_or_scheme)
|
||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
local EMPTY = { '-', 'CanolaEmpty' }
|
local EMPTY = { "-", "OilEmpty" }
|
||||||
|
|
||||||
M.EMPTY = EMPTY
|
M.EMPTY = EMPTY
|
||||||
|
|
||||||
---@param adapter canola.Adapter
|
---@param adapter oil.Adapter
|
||||||
---@param col_def canola.ColumnSpec
|
---@param col_def oil.ColumnSpec
|
||||||
---@param entry canola.InternalEntry
|
---@param entry oil.InternalEntry
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
---@return canola.TextChunk
|
---@return oil.TextChunk
|
||||||
M.render_col = function(adapter, col_def, entry, bufnr)
|
M.render_col = function(adapter, col_def, entry, bufnr)
|
||||||
local name, conf = util.split_config(col_def)
|
local name, conf = util.split_config(col_def)
|
||||||
local column = M.get_column(adapter, name)
|
local column = M.get_column(adapter, name)
|
||||||
|
|
@ -71,17 +71,17 @@ M.render_col = function(adapter, col_def, entry, bufnr)
|
||||||
end
|
end
|
||||||
|
|
||||||
local chunk = column.render(entry, conf, bufnr)
|
local chunk = column.render(entry, conf, bufnr)
|
||||||
if type(chunk) == 'table' then
|
if type(chunk) == "table" then
|
||||||
if chunk[1]:match('^%s*$') then
|
if chunk[1]:match("^%s*$") then
|
||||||
return EMPTY
|
return EMPTY
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
if not chunk or chunk:match('^%s*$') then
|
if not chunk or chunk:match("^%s*$") then
|
||||||
return EMPTY
|
return EMPTY
|
||||||
end
|
end
|
||||||
if conf and conf.highlight then
|
if conf and conf.highlight then
|
||||||
local highlight = conf.highlight
|
local highlight = conf.highlight
|
||||||
if type(highlight) == 'function' then
|
if type(highlight) == "function" then
|
||||||
highlight = conf.highlight(chunk)
|
highlight = conf.highlight(chunk)
|
||||||
end
|
end
|
||||||
return { chunk, highlight }
|
return { chunk, highlight }
|
||||||
|
|
@ -90,27 +90,27 @@ M.render_col = function(adapter, col_def, entry, bufnr)
|
||||||
return chunk
|
return chunk
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param adapter canola.Adapter
|
---@param adapter oil.Adapter
|
||||||
---@param line string
|
---@param line string
|
||||||
---@param col_def canola.ColumnSpec
|
---@param col_def oil.ColumnSpec
|
||||||
---@return nil|string
|
---@return nil|string
|
||||||
---@return nil|string
|
---@return nil|string
|
||||||
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*(-%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:gsub('^%s+', ''), conf)
|
return column.parse(line:gsub("^%s+", ""), conf)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param adapter canola.Adapter
|
---@param adapter oil.Adapter
|
||||||
---@param col_name string
|
---@param col_name string
|
||||||
---@param entry canola.InternalEntry
|
---@param entry oil.InternalEntry
|
||||||
---@param parsed_value any
|
---@param parsed_value any
|
||||||
---@return boolean
|
---@return boolean
|
||||||
M.compare = function(adapter, col_name, entry, parsed_value)
|
M.compare = function(adapter, col_name, entry, parsed_value)
|
||||||
|
|
@ -122,29 +122,29 @@ M.compare = function(adapter, col_name, entry, parsed_value)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param adapter canola.Adapter
|
---@param adapter oil.Adapter
|
||||||
---@param action canola.ChangeAction
|
---@param action oil.ChangeAction
|
||||||
---@return string
|
---@return string
|
||||||
M.render_change_action = function(adapter, action)
|
M.render_change_action = function(adapter, action)
|
||||||
local column = M.get_column(adapter, action.column)
|
local column = M.get_column(adapter, action.column)
|
||||||
if not column then
|
if not column then
|
||||||
error(string.format('Received change action for nonexistant column %s', action.column))
|
error(string.format("Received change action for nonexistant column %s", action.column))
|
||||||
end
|
end
|
||||||
if column.render_action then
|
if column.render_action then
|
||||||
return column.render_action(action)
|
return column.render_action(action)
|
||||||
else
|
else
|
||||||
return string.format('CHANGE %s %s = %s', action.url, action.column, action.value)
|
return string.format("CHANGE %s %s = %s", action.url, action.column, action.value)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param adapter canola.Adapter
|
---@param adapter oil.Adapter
|
||||||
---@param action canola.ChangeAction
|
---@param action oil.ChangeAction
|
||||||
---@param callback fun(err: nil|string)
|
---@param callback fun(err: nil|string)
|
||||||
M.perform_change_action = function(adapter, action, callback)
|
M.perform_change_action = function(adapter, action, callback)
|
||||||
local column = M.get_column(adapter, action.column)
|
local column = M.get_column(adapter, action.column)
|
||||||
if not column then
|
if not column then
|
||||||
return callback(
|
return callback(
|
||||||
string.format('Received change action for nonexistant column %s', action.column)
|
string.format("Received change action for nonexistant column %s", action.column)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
column.perform_action(action, callback)
|
column.perform_action(action, callback)
|
||||||
|
|
@ -152,12 +152,12 @@ end
|
||||||
|
|
||||||
local icon_provider = util.get_icon_provider()
|
local icon_provider = util.get_icon_provider()
|
||||||
if icon_provider then
|
if icon_provider then
|
||||||
M.register('icon', {
|
M.register("icon", {
|
||||||
render = function(entry, conf, bufnr)
|
render = function(entry, conf, bufnr)
|
||||||
local field_type = entry[FIELD_TYPE]
|
local field_type = entry[FIELD_TYPE]
|
||||||
local name = entry[FIELD_NAME]
|
local name = entry[FIELD_NAME]
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
if field_type == 'link' and meta then
|
if field_type == "link" and meta then
|
||||||
if meta.link then
|
if meta.link then
|
||||||
name = meta.link
|
name = meta.link
|
||||||
end
|
end
|
||||||
|
|
@ -170,11 +170,11 @@ if icon_provider then
|
||||||
end
|
end
|
||||||
|
|
||||||
local ft = nil
|
local ft = nil
|
||||||
if conf and conf.use_slow_filetype_detection and field_type == 'file' then
|
if conf and conf.use_slow_filetype_detection and field_type == "file" then
|
||||||
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
||||||
local _, path = util.parse_url(bufname)
|
local _, path = util.parse_url(bufname)
|
||||||
if path then
|
if path then
|
||||||
local lines = vim.fn.readfile(path .. name, '', 16)
|
local lines = vim.fn.readfile(path .. name, "", 16)
|
||||||
if lines and #lines > 0 then
|
if lines and #lines > 0 then
|
||||||
ft = vim.filetype.match({ filename = name, contents = lines })
|
ft = vim.filetype.match({ filename = name, contents = lines })
|
||||||
end
|
end
|
||||||
|
|
@ -183,10 +183,10 @@ if icon_provider then
|
||||||
|
|
||||||
local icon, hl = icon_provider(field_type, name, conf, ft)
|
local icon, hl = icon_provider(field_type, name, conf, ft)
|
||||||
if not conf or conf.add_padding ~= false then
|
if not conf or conf.add_padding ~= false then
|
||||||
icon = icon .. ' '
|
icon = icon .. " "
|
||||||
end
|
end
|
||||||
if conf and conf.highlight then
|
if conf and conf.highlight then
|
||||||
if type(conf.highlight) == 'function' then
|
if type(conf.highlight) == "function" then
|
||||||
hl = conf.highlight(icon)
|
hl = conf.highlight(icon)
|
||||||
else
|
else
|
||||||
hl = conf.highlight
|
hl = conf.highlight
|
||||||
|
|
@ -196,29 +196,29 @@ if icon_provider then
|
||||||
end,
|
end,
|
||||||
|
|
||||||
parse = function(line, conf)
|
parse = function(line, conf)
|
||||||
return line:match('^(%S+)%s+(.*)$')
|
return line:match("^(%S+)%s+(.*)$")
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
local default_type_icons = {
|
local default_type_icons = {
|
||||||
directory = 'dir',
|
directory = "dir",
|
||||||
socket = 'sock',
|
socket = "sock",
|
||||||
}
|
}
|
||||||
---@param entry canola.InternalEntry
|
---@param entry oil.InternalEntry
|
||||||
---@return boolean
|
---@return boolean
|
||||||
local function is_entry_directory(entry)
|
local function is_entry_directory(entry)
|
||||||
local type = entry[FIELD_TYPE]
|
local type = entry[FIELD_TYPE]
|
||||||
if type == 'directory' then
|
if type == "directory" then
|
||||||
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') == true
|
return (meta and meta.link_stat and meta.link_stat.type == "directory") == true
|
||||||
else
|
else
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
M.register('type', {
|
M.register("type", {
|
||||||
render = function(entry, conf)
|
render = function(entry, conf)
|
||||||
local entry_type = entry[FIELD_TYPE]
|
local entry_type = entry[FIELD_TYPE]
|
||||||
if conf and conf.icons then
|
if conf and conf.icons then
|
||||||
|
|
@ -229,7 +229,7 @@ M.register('type', {
|
||||||
end,
|
end,
|
||||||
|
|
||||||
parse = function(line, conf)
|
parse = function(line, conf)
|
||||||
return line:match('^(%S+)%s+(.*)$')
|
return line:match("^(%S+)%s+(.*)$")
|
||||||
end,
|
end,
|
||||||
|
|
||||||
get_sort_value = function(entry)
|
get_sort_value = function(entry)
|
||||||
|
|
@ -242,22 +242,22 @@ M.register('type', {
|
||||||
})
|
})
|
||||||
|
|
||||||
local function adjust_number(int)
|
local function adjust_number(int)
|
||||||
return string.format('%03d%s', #int, int)
|
return string.format("%03d%s", #int, int)
|
||||||
end
|
end
|
||||||
|
|
||||||
M.register('name', {
|
M.register("name", {
|
||||||
render = function(entry, conf)
|
render = function(entry, conf)
|
||||||
error('Do not use the name column. It is for sorting only')
|
error("Do not use the name column. It is for sorting only")
|
||||||
end,
|
end,
|
||||||
|
|
||||||
parse = function(line, conf)
|
parse = function(line, conf)
|
||||||
error('Do not use the name column. It is for sorting only')
|
error("Do not use the name column. It is for sorting only")
|
||||||
end,
|
end,
|
||||||
|
|
||||||
create_sort_value_factory = function(num_entries)
|
create_sort_value_factory = function(num_entries)
|
||||||
if
|
if
|
||||||
config.view_options.natural_order == false
|
config.view_options.natural_order == false
|
||||||
or (config.view_options.natural_order == 'fast' and num_entries > 5000)
|
or (config.view_options.natural_order == "fast" and num_entries > 5000)
|
||||||
then
|
then
|
||||||
if config.view_options.case_insensitive then
|
if config.view_options.case_insensitive then
|
||||||
return function(entry)
|
return function(entry)
|
||||||
|
|
@ -272,7 +272,7 @@ M.register('name', {
|
||||||
local memo = {}
|
local memo = {}
|
||||||
return function(entry)
|
return function(entry)
|
||||||
if memo[entry] == nil then
|
if memo[entry] == nil then
|
||||||
local name = entry[FIELD_NAME]:gsub('0*(%d+)', adjust_number)
|
local name = entry[FIELD_NAME]:gsub("0*(%d+)", adjust_number)
|
||||||
if config.view_options.case_insensitive then
|
if config.view_options.case_insensitive then
|
||||||
name = name:lower()
|
name = name:lower()
|
||||||
end
|
end
|
||||||
|
|
@ -1,45 +1,41 @@
|
||||||
local default_config = {
|
local default_config = {
|
||||||
-- Canola will take over directory buffers (e.g. `vim .` or `:e src/`)
|
-- Oil will take over directory buffers (e.g. `vim .` or `:e src/`)
|
||||||
-- Set to false if you want some other plugin (e.g. netrw) to open when you edit directories.
|
-- Set to false if you want some other plugin (e.g. netrw) to open when you edit directories.
|
||||||
default_file_explorer = true,
|
default_file_explorer = true,
|
||||||
-- Id is automatically added at the beginning, and name at the end
|
-- Id is automatically added at the beginning, and name at the end
|
||||||
-- See :help canola-columns
|
-- See :help oil-columns
|
||||||
columns = {
|
columns = {
|
||||||
'icon',
|
"icon",
|
||||||
-- "permissions",
|
-- "permissions",
|
||||||
-- "size",
|
-- "size",
|
||||||
-- "mtime",
|
-- "mtime",
|
||||||
},
|
},
|
||||||
-- Buffer-local options to use for canola buffers
|
-- Buffer-local options to use for oil buffers
|
||||||
buf_options = {
|
buf_options = {
|
||||||
buflisted = false,
|
buflisted = false,
|
||||||
bufhidden = 'hide',
|
bufhidden = "hide",
|
||||||
},
|
},
|
||||||
-- Window-local options to use for canola buffers
|
-- Window-local options to use for oil buffers
|
||||||
win_options = {
|
win_options = {
|
||||||
wrap = false,
|
wrap = false,
|
||||||
signcolumn = 'no',
|
signcolumn = "no",
|
||||||
cursorcolumn = false,
|
cursorcolumn = false,
|
||||||
foldcolumn = '0',
|
foldcolumn = "0",
|
||||||
spell = false,
|
spell = false,
|
||||||
list = false,
|
list = false,
|
||||||
conceallevel = 3,
|
conceallevel = 3,
|
||||||
concealcursor = 'nvic',
|
concealcursor = "nvic",
|
||||||
},
|
},
|
||||||
-- Send deleted files to the trash instead of permanently deleting them (:help canola-trash)
|
-- Send deleted files to the trash instead of permanently deleting them (:help oil-trash)
|
||||||
delete_to_trash = false,
|
delete_to_trash = false,
|
||||||
-- Wipe open buffers for files deleted via canola (:help canola.cleanup_buffers_on_delete)
|
-- Skip the confirmation popup for simple operations (:help oil.skip_confirm_for_simple_edits)
|
||||||
cleanup_buffers_on_delete = false,
|
|
||||||
-- Skip the confirmation popup for simple operations (:help canola.skip_confirm_for_simple_edits)
|
|
||||||
skip_confirm_for_simple_edits = false,
|
skip_confirm_for_simple_edits = false,
|
||||||
skip_confirm_for_delete = false,
|
|
||||||
-- Selecting a new/moved/renamed file or directory will prompt you to save changes first
|
-- Selecting a new/moved/renamed file or directory will prompt you to save changes first
|
||||||
-- (:help prompt_save_on_select_new_entry)
|
-- (:help prompt_save_on_select_new_entry)
|
||||||
prompt_save_on_select_new_entry = true,
|
prompt_save_on_select_new_entry = true,
|
||||||
auto_save_on_select_new_entry = false,
|
-- Oil will automatically delete hidden buffers after this delay
|
||||||
-- Canola will automatically delete hidden buffers after this delay
|
|
||||||
-- You can set the delay to false to disable cleanup entirely
|
-- You can set the delay to false to disable cleanup entirely
|
||||||
-- Note that the cleanup process only starts when none of the canola buffers are currently displayed
|
-- Note that the cleanup process only starts when none of the oil buffers are currently displayed
|
||||||
cleanup_delay_ms = 2000,
|
cleanup_delay_ms = 2000,
|
||||||
lsp_file_methods = {
|
lsp_file_methods = {
|
||||||
-- Enable or disable LSP file operations
|
-- Enable or disable LSP file operations
|
||||||
|
|
@ -50,44 +46,43 @@ local default_config = {
|
||||||
-- Set to "unmodified" to only save unmodified buffers
|
-- Set to "unmodified" to only save unmodified buffers
|
||||||
autosave_changes = false,
|
autosave_changes = false,
|
||||||
},
|
},
|
||||||
-- Constrain the cursor to the editable parts of the canola buffer
|
-- Constrain the cursor to the editable parts of the oil buffer
|
||||||
-- Set to `false` to disable, or "name" to keep it on the file names
|
-- Set to `false` to disable, or "name" to keep it on the file names
|
||||||
constrain_cursor = 'editable',
|
constrain_cursor = "editable",
|
||||||
-- Set to true to watch the filesystem for changes and reload canola
|
-- Set to true to watch the filesystem for changes and reload oil
|
||||||
watch_for_changes = false,
|
watch_for_changes = false,
|
||||||
-- Keymaps in canola buffer. Can be any value that `vim.keymap.set` accepts OR a table of keymap
|
-- Keymaps in oil buffer. Can be any value that `vim.keymap.set` accepts OR a table of keymap
|
||||||
-- options with a `callback` (e.g. { callback = function() ... end, desc = "", mode = "n" })
|
-- options with a `callback` (e.g. { callback = function() ... end, desc = "", mode = "n" })
|
||||||
-- Additionally, if it is a string that matches "actions.<name>",
|
-- Additionally, if it is a string that matches "actions.<name>",
|
||||||
-- it will use the mapping at require("canola.actions").<name>
|
-- it will use the mapping at require("oil.actions").<name>
|
||||||
-- Set to `false` to remove a keymap
|
-- Set to `false` to remove a keymap
|
||||||
-- See :help canola-actions for a list of all available actions
|
-- See :help oil-actions for a list of all available actions
|
||||||
keymaps = {
|
keymaps = {
|
||||||
['g?'] = { 'actions.show_help', mode = 'n' },
|
["g?"] = { "actions.show_help", mode = "n" },
|
||||||
['<CR>'] = 'actions.select',
|
["<CR>"] = "actions.select",
|
||||||
['<C-s>'] = { 'actions.select', opts = { vertical = true } },
|
["<C-s>"] = { "actions.select", opts = { vertical = true } },
|
||||||
['<C-h>'] = { 'actions.select', opts = { horizontal = true } },
|
["<C-h>"] = { "actions.select", opts = { horizontal = true } },
|
||||||
['<C-t>'] = { 'actions.select', opts = { tab = true } },
|
["<C-t>"] = { "actions.select", opts = { tab = true } },
|
||||||
['<C-p>'] = 'actions.preview',
|
["<C-p>"] = "actions.preview",
|
||||||
['<C-c>'] = { 'actions.close', mode = 'n' },
|
["<C-c>"] = { "actions.close", mode = "n" },
|
||||||
['<C-l>'] = 'actions.refresh',
|
["<C-l>"] = "actions.refresh",
|
||||||
['-'] = { '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" },
|
||||||
['g~'] = { '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" },
|
||||||
['g\\'] = { 'actions.toggle_trash', mode = 'n' },
|
["g\\"] = { "actions.toggle_trash", mode = "n" },
|
||||||
},
|
},
|
||||||
-- Set to false to disable all of the above keymaps
|
-- Set to false to disable all of the above keymaps
|
||||||
use_default_keymaps = true,
|
use_default_keymaps = true,
|
||||||
view_options = {
|
view_options = {
|
||||||
-- Show files and directories that start with "."
|
-- Show files and directories that start with "."
|
||||||
show_hidden = false,
|
show_hidden = false,
|
||||||
show_hidden_when_empty = false,
|
|
||||||
-- This function defines what is considered a "hidden" file
|
-- This function defines what is considered a "hidden" file
|
||||||
is_hidden_file = function(name, bufnr)
|
is_hidden_file = function(name, bufnr)
|
||||||
local m = name:match('^%.')
|
local m = name:match("^%.")
|
||||||
return m ~= nil
|
return m ~= nil
|
||||||
end,
|
end,
|
||||||
-- This function defines what will never be shown, even when `show_hidden` is set
|
-- This function defines what will never be shown, even when `show_hidden` is set
|
||||||
|
|
@ -96,14 +91,14 @@ local default_config = {
|
||||||
end,
|
end,
|
||||||
-- Sort file names with numbers in a more intuitive order for humans.
|
-- Sort file names with numbers in a more intuitive order for humans.
|
||||||
-- Can be "fast", true, or false. "fast" will turn it off for large directories.
|
-- Can be "fast", true, or false. "fast" will turn it off for large directories.
|
||||||
natural_order = 'fast',
|
natural_order = "fast",
|
||||||
-- Sort file and directory names case insensitive
|
-- Sort file and directory names case insensitive
|
||||||
case_insensitive = false,
|
case_insensitive = false,
|
||||||
sort = {
|
sort = {
|
||||||
-- sort order can be "asc" or "desc"
|
-- sort order can be "asc" or "desc"
|
||||||
-- see :help canola-columns to see which columns are sortable
|
-- see :help oil-columns to see which columns are sortable
|
||||||
{ 'type', 'asc' },
|
{ "type", "asc" },
|
||||||
{ 'name', 'asc' },
|
{ "name", "asc" },
|
||||||
},
|
},
|
||||||
-- Customize the highlight group for the file name
|
-- Customize the highlight group for the file name
|
||||||
highlight_filename = function(entry, is_hidden, is_link_target, is_link_orphan)
|
highlight_filename = function(entry, is_hidden, is_link_target, is_link_orphan)
|
||||||
|
|
@ -129,7 +124,7 @@ local default_config = {
|
||||||
return false
|
return false
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
-- Configuration for the floating window in canola.open_float
|
-- Configuration for the floating window in oil.open_float
|
||||||
float = {
|
float = {
|
||||||
-- Padding around the floating window
|
-- Padding around the floating window
|
||||||
padding = 2,
|
padding = 2,
|
||||||
|
|
@ -140,10 +135,10 @@ local default_config = {
|
||||||
win_options = {
|
win_options = {
|
||||||
winblend = 0,
|
winblend = 0,
|
||||||
},
|
},
|
||||||
-- optionally override the canola buffers window title with custom function: fun(winid: integer): string
|
-- optionally override the oil buffers window title with custom function: fun(winid: integer): string
|
||||||
get_win_title = nil,
|
get_win_title = nil,
|
||||||
-- preview_split: Split direction: "auto", "left", "right", "above", "below".
|
-- preview_split: Split direction: "auto", "left", "right", "above", "below".
|
||||||
preview_split = 'auto',
|
preview_split = "auto",
|
||||||
-- This is the config that will be passed to nvim_open_win.
|
-- This is the config that will be passed to nvim_open_win.
|
||||||
-- Change values here to customize the layout
|
-- Change values here to customize the layout
|
||||||
override = function(conf)
|
override = function(conf)
|
||||||
|
|
@ -155,12 +150,11 @@ local default_config = {
|
||||||
-- Whether the preview window is automatically updated when the cursor is moved
|
-- Whether the preview window is automatically updated when the cursor is moved
|
||||||
update_on_cursor_moved = true,
|
update_on_cursor_moved = true,
|
||||||
-- How to open the preview window "load"|"scratch"|"fast_scratch"
|
-- How to open the preview window "load"|"scratch"|"fast_scratch"
|
||||||
preview_method = 'fast_scratch',
|
preview_method = "fast_scratch",
|
||||||
-- A function that returns true to disable preview on a file e.g. to avoid lag
|
-- A function that returns true to disable preview on a file e.g. to avoid lag
|
||||||
disable_preview = function(filename)
|
disable_preview = function(filename)
|
||||||
return false
|
return false
|
||||||
end,
|
end,
|
||||||
max_file_size = 10,
|
|
||||||
-- Window-local options to use for preview window buffers
|
-- Window-local options to use for preview window buffers
|
||||||
win_options = {},
|
win_options = {},
|
||||||
},
|
},
|
||||||
|
|
@ -196,7 +190,7 @@ local default_config = {
|
||||||
min_height = { 5, 0.1 },
|
min_height = { 5, 0.1 },
|
||||||
height = nil,
|
height = nil,
|
||||||
border = nil,
|
border = nil,
|
||||||
minimized_border = 'none',
|
minimized_border = "none",
|
||||||
win_options = {
|
win_options = {
|
||||||
winblend = 0,
|
winblend = 0,
|
||||||
},
|
},
|
||||||
|
|
@ -215,187 +209,177 @@ local default_config = {
|
||||||
-- 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 "canola-s3://" on older neovim versions, since it doesn't open buffers correctly with a number
|
-- not "oil-s3://" on older neovim versions, since it doesn't open buffers correctly with a number
|
||||||
-- in the name
|
-- in the name
|
||||||
local canola_s3_string = vim.fn.has('nvim-0.12') == 1 and 'canola-s3://' or 'canola-sss://'
|
local oil_s3_string = vim.fn.has("nvim-0.12") == 1 and "oil-s3://" or "oil-sss://"
|
||||||
default_config.adapters = {
|
default_config.adapters = {
|
||||||
['canola://'] = 'files',
|
["oil://"] = "files",
|
||||||
['canola-ssh://'] = 'ssh',
|
["oil-ssh://"] = "ssh",
|
||||||
[canola_s3_string] = 's3',
|
[oil_s3_string] = "s3",
|
||||||
['canola-trash://'] = 'trash',
|
["oil-trash://"] = "trash",
|
||||||
}
|
}
|
||||||
default_config.adapter_aliases = {}
|
default_config.adapter_aliases = {}
|
||||||
-- We want the function in the default config for documentation generation, but if we nil it out
|
-- We want the function in the default config for documentation generation, but if we nil it out
|
||||||
-- here we can get some performance wins
|
-- here we can get some performance wins
|
||||||
default_config.view_options.highlight_filename = nil
|
default_config.view_options.highlight_filename = nil
|
||||||
|
|
||||||
---@class canola.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 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 canola.ColumnSpec[]
|
---@field columns oil.ColumnSpec[]
|
||||||
---@field buf_options table<string, any>
|
---@field buf_options table<string, any>
|
||||||
---@field win_options table<string, any>
|
---@field win_options table<string, any>
|
||||||
---@field delete_to_trash boolean
|
---@field delete_to_trash boolean
|
||||||
---@field cleanup_buffers_on_delete boolean
|
|
||||||
---@field skip_confirm_for_simple_edits boolean
|
---@field skip_confirm_for_simple_edits boolean
|
||||||
---@field skip_confirm_for_delete boolean
|
|
||||||
---@field prompt_save_on_select_new_entry boolean
|
---@field prompt_save_on_select_new_entry boolean
|
||||||
---@field auto_save_on_select_new_entry boolean
|
|
||||||
---@field cleanup_delay_ms integer
|
---@field cleanup_delay_ms integer
|
||||||
---@field lsp_file_methods canola.LspFileMethods
|
---@field lsp_file_methods oil.LspFileMethods
|
||||||
---@field constrain_cursor false|"name"|"editable"
|
---@field constrain_cursor false|"name"|"editable"
|
||||||
---@field watch_for_changes boolean
|
---@field watch_for_changes boolean
|
||||||
---@field keymaps table<string, any>
|
---@field keymaps table<string, any>
|
||||||
---@field use_default_keymaps boolean
|
---@field use_default_keymaps boolean
|
||||||
---@field view_options canola.ViewOptions
|
---@field view_options oil.ViewOptions
|
||||||
---@field new_file_mode integer
|
---@field new_file_mode integer
|
||||||
---@field new_dir_mode integer
|
---@field new_dir_mode integer
|
||||||
---@field extra_scp_args string[]
|
---@field extra_scp_args string[]
|
||||||
---@field extra_s3_args string[]
|
---@field extra_s3_args string[]
|
||||||
---@field git canola.GitOptions
|
---@field git oil.GitOptions
|
||||||
---@field float canola.FloatWindowConfig
|
---@field float oil.FloatWindowConfig
|
||||||
---@field preview_win canola.PreviewWindowConfig
|
---@field preview_win oil.PreviewWindowConfig
|
||||||
---@field confirmation canola.ConfirmationWindowConfig
|
---@field confirmation oil.ConfirmationWindowConfig
|
||||||
---@field progress canola.ProgressWindowConfig
|
---@field progress oil.ProgressWindowConfig
|
||||||
---@field ssh canola.SimpleWindowConfig
|
---@field ssh oil.SimpleWindowConfig
|
||||||
---@field keymaps_help canola.SimpleWindowConfig
|
---@field keymaps_help oil.SimpleWindowConfig
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
-- For backwards compatibility
|
-- For backwards compatibility
|
||||||
---@alias canola.setupOpts canola.SetupOpts
|
---@alias oil.setupOpts oil.SetupOpts
|
||||||
|
|
||||||
---@class (exact) canola.SetupOpts
|
---@class (exact) oil.SetupOpts
|
||||||
---@field default_file_explorer? boolean Canola will take over directory buffers (e.g. `vim .` or `:e src/`). Set to false if you still want to use netrw.
|
---@field default_file_explorer? boolean Oil will take over directory buffers (e.g. `vim .` or `:e src/`). Set to false if you still want to use netrw.
|
||||||
---@field columns? canola.ColumnSpec[] The columns to display. See :help canola-columns.
|
---@field columns? oil.ColumnSpec[] The columns to display. See :help oil-columns.
|
||||||
---@field buf_options? table<string, any> Buffer-local options to use for canola buffers
|
---@field buf_options? table<string, any> Buffer-local options to use for oil buffers
|
||||||
---@field win_options? table<string, any> Window-local options to use for canola buffers
|
---@field win_options? table<string, any> Window-local options to use for oil buffers
|
||||||
---@field delete_to_trash? boolean Send deleted files to the trash instead of permanently deleting them (:help canola-trash).
|
---@field delete_to_trash? boolean Send deleted files to the trash instead of permanently deleting them (:help oil-trash).
|
||||||
---@field cleanup_buffers_on_delete? boolean Wipe open buffers for files deleted via canola (:help canola.cleanup_buffers_on_delete).
|
---@field skip_confirm_for_simple_edits? boolean Skip the confirmation popup for simple operations (:help oil.skip_confirm_for_simple_edits).
|
||||||
---@field skip_confirm_for_simple_edits? boolean Skip the confirmation popup for simple operations (:help canola.skip_confirm_for_simple_edits).
|
|
||||||
---@field skip_confirm_for_delete? boolean Skip the confirmation popup when all pending actions are deletes (:help canola.skip_confirm_for_delete).
|
|
||||||
---@field prompt_save_on_select_new_entry? boolean Selecting a new/moved/renamed file or directory will prompt you to save changes first (:help prompt_save_on_select_new_entry).
|
---@field prompt_save_on_select_new_entry? boolean Selecting a new/moved/renamed file or directory will prompt you to save changes first (:help prompt_save_on_select_new_entry).
|
||||||
---@field auto_save_on_select_new_entry? boolean Automatically save changes when selecting a new/moved/renamed entry, instead of prompting (:help canola.auto_save_on_select_new_entry).
|
---@field cleanup_delay_ms? integer Oil will automatically delete hidden buffers after this delay. You can set the delay to false to disable cleanup entirely. Note that the cleanup process only starts when none of the oil buffers are currently displayed.
|
||||||
---@field cleanup_delay_ms? integer Canola will automatically delete hidden buffers after this delay. You can set the delay to false to disable cleanup entirely. Note that the cleanup process only starts when none of the canola buffers are currently displayed.
|
---@field lsp_file_methods? oil.SetupLspFileMethods Configure LSP file operation integration.
|
||||||
---@field lsp_file_methods? canola.SetupLspFileMethods Configure LSP file operation integration.
|
---@field constrain_cursor? false|"name"|"editable" Constrain the cursor to the editable parts of the oil buffer. Set to `false` to disable, or "name" to keep it on the file names.
|
||||||
---@field constrain_cursor? false|"name"|"editable" Constrain the cursor to the editable parts of the canola buffer. Set to `false` to disable, or "name" to keep it on the file names.
|
---@field watch_for_changes? boolean Set to true to watch the filesystem for changes and reload oil.
|
||||||
---@field watch_for_changes? boolean Set to true to watch the filesystem for changes and reload canola.
|
|
||||||
---@field keymaps? table<string, any>
|
---@field keymaps? table<string, any>
|
||||||
---@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? canola.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 new_file_mode? integer Permission mode for new files in decimal (default 420 = 0644)
|
---@field new_file_mode? integer Permission mode for new files in decimal (default 420 = 0644)
|
||||||
---@field new_dir_mode? integer Permission mode for new directories in decimal (default 493 = 0755)
|
---@field new_dir_mode? integer Permission mode for new directories in decimal (default 493 = 0755)
|
||||||
---@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 extra_s3_args? string[] Extra arguments to pass to aws s3 when moving/copying files using aws s3
|
||||||
---@field git? canola.SetupGitOptions EXPERIMENTAL support for performing file operations with git
|
---@field git? oil.SetupGitOptions EXPERIMENTAL support for performing file operations with git
|
||||||
---@field float? canola.SetupFloatWindowConfig Configuration for the floating window in canola.open_float
|
---@field float? oil.SetupFloatWindowConfig Configuration for the floating window in oil.open_float
|
||||||
---@field preview_win? canola.SetupPreviewWindowConfig Configuration for the file preview window
|
---@field preview_win? oil.SetupPreviewWindowConfig Configuration for the file preview window
|
||||||
---@field confirmation? canola.SetupConfirmationWindowConfig Configuration for the floating action confirmation window
|
---@field confirmation? oil.SetupConfirmationWindowConfig Configuration for the floating action confirmation window
|
||||||
---@field progress? canola.SetupProgressWindowConfig Configuration for the floating progress window
|
---@field progress? oil.SetupProgressWindowConfig Configuration for the floating progress window
|
||||||
---@field ssh? canola.SetupSimpleWindowConfig Configuration for the floating SSH window
|
---@field ssh? oil.SetupSimpleWindowConfig Configuration for the floating SSH window
|
||||||
---@field keymaps_help? canola.SetupSimpleWindowConfig Configuration for the floating keymaps help window
|
---@field keymaps_help? oil.SetupSimpleWindowConfig Configuration for the floating keymaps help window
|
||||||
|
|
||||||
---@class (exact) canola.LspFileMethods
|
---@class (exact) oil.LspFileMethods
|
||||||
---@field enabled boolean
|
---@field enabled boolean
|
||||||
---@field timeout_ms integer
|
---@field timeout_ms integer
|
||||||
---@field autosave_changes boolean|"unmodified" Set to true to autosave buffers that are updated with LSP willRenameFiles. Set to "unmodified" to only save unmodified buffers.
|
---@field autosave_changes boolean|"unmodified" Set to true to autosave buffers that are updated with LSP willRenameFiles. Set to "unmodified" to only save unmodified buffers.
|
||||||
|
|
||||||
---@class (exact) canola.SetupLspFileMethods
|
---@class (exact) oil.SetupLspFileMethods
|
||||||
---@field enabled? boolean Enable or disable LSP file operations
|
---@field enabled? boolean Enable or disable LSP file operations
|
||||||
---@field timeout_ms? integer Time to wait for LSP file operations to complete before skipping.
|
---@field timeout_ms? integer Time to wait for LSP file operations to complete before skipping.
|
||||||
---@field autosave_changes? boolean|"unmodified" Set to true to autosave buffers that are updated with LSP willRenameFiles. Set to "unmodified" to only save unmodified buffers.
|
---@field autosave_changes? boolean|"unmodified" Set to true to autosave buffers that are updated with LSP willRenameFiles. Set to "unmodified" to only save unmodified buffers.
|
||||||
|
|
||||||
---@class (exact) canola.ViewOptions
|
---@class (exact) oil.ViewOptions
|
||||||
---@field show_hidden boolean
|
---@field show_hidden boolean
|
||||||
---@field show_hidden_when_empty boolean
|
---@field is_hidden_file fun(name: string, bufnr: integer, entry: oil.Entry): boolean
|
||||||
---@field is_hidden_file fun(name: string, bufnr: integer, entry: canola.Entry): boolean
|
---@field is_always_hidden fun(name: string, bufnr: integer, entry: oil.Entry): boolean
|
||||||
---@field is_always_hidden fun(name: string, bufnr: integer, entry: canola.Entry): boolean
|
|
||||||
---@field natural_order boolean|"fast"
|
---@field natural_order boolean|"fast"
|
||||||
---@field case_insensitive boolean
|
---@field case_insensitive boolean
|
||||||
---@field sort canola.SortSpec[]
|
---@field sort oil.SortSpec[]
|
||||||
---@field highlight_filename? fun(entry: canola.Entry, is_hidden: boolean, is_link_target: boolean, is_link_orphan: boolean, bufnr: integer): string|nil
|
---@field highlight_filename? fun(entry: oil.Entry, is_hidden: boolean, is_link_target: boolean, is_link_orphan: boolean, bufnr: integer): string|nil
|
||||||
|
|
||||||
---@class (exact) canola.SetupViewOptions
|
---@class (exact) oil.SetupViewOptions
|
||||||
---@field show_hidden? boolean Show files and directories that start with "."
|
---@field show_hidden? boolean Show files and directories that start with "."
|
||||||
---@field show_hidden_when_empty? boolean When true and the directory has no visible entries, show hidden entries instead of an empty listing (:help canola.show_hidden_when_empty).
|
|
||||||
---@field is_hidden_file? fun(name: string, bufnr: integer): boolean This function defines what is considered a "hidden" file
|
---@field is_hidden_file? fun(name: string, bufnr: integer): boolean This function defines what is considered a "hidden" file
|
||||||
---@field is_always_hidden? fun(name: string, bufnr: integer): boolean This function defines what will never be shown, even when `show_hidden` is set
|
---@field is_always_hidden? fun(name: string, bufnr: integer): boolean This function defines what will never be shown, even when `show_hidden` is set
|
||||||
---@field natural_order? boolean|"fast" Sort file names with numbers in a more intuitive order for humans. Can be slow for large directories.
|
---@field natural_order? boolean|"fast" Sort file names with numbers in a more intuitive order for humans. Can be slow for large directories.
|
||||||
---@field case_insensitive? boolean Sort file and directory names case insensitive
|
---@field case_insensitive? boolean Sort file and directory names case insensitive
|
||||||
---@field sort? canola.SortSpec[] Sort order for the file list
|
---@field sort? oil.SortSpec[] Sort order for the file list
|
||||||
---@field highlight_filename? fun(entry: canola.Entry, is_hidden: boolean, is_link_target: boolean, is_link_orphan: boolean): string|nil Customize the highlight group for the file name
|
---@field highlight_filename? fun(entry: oil.Entry, is_hidden: boolean, is_link_target: boolean, is_link_orphan: boolean): string|nil Customize the highlight group for the file name
|
||||||
|
|
||||||
---@class (exact) canola.SortSpec
|
---@class (exact) oil.SortSpec
|
||||||
---@field [1] string
|
---@field [1] string
|
||||||
---@field [2] "asc"|"desc"
|
---@field [2] "asc"|"desc"
|
||||||
|
|
||||||
---@class (exact) canola.GitOptions
|
---@class (exact) oil.GitOptions
|
||||||
---@field add fun(path: string): boolean
|
---@field add fun(path: string): boolean
|
||||||
---@field mv fun(src_path: string, dest_path: string): boolean
|
---@field mv fun(src_path: string, dest_path: string): boolean
|
||||||
---@field rm fun(path: string): boolean
|
---@field rm fun(path: string): boolean
|
||||||
|
|
||||||
---@class (exact) canola.SetupGitOptions
|
---@class (exact) oil.SetupGitOptions
|
||||||
---@field add? fun(path: string): boolean Return true to automatically git add a new file
|
---@field add? fun(path: string): boolean Return true to automatically git add a new file
|
||||||
---@field mv? fun(src_path: string, dest_path: string): boolean Return true to automatically git mv a moved file
|
---@field mv? fun(src_path: string, dest_path: string): boolean Return true to automatically git mv a moved file
|
||||||
---@field rm? fun(path: string): boolean Return true to automatically git rm a deleted file
|
---@field rm? fun(path: string): boolean Return true to automatically git rm a deleted file
|
||||||
|
|
||||||
---@class (exact) canola.WindowDimensionDualConstraint
|
---@class (exact) oil.WindowDimensionDualConstraint
|
||||||
---@field [1] number
|
---@field [1] number
|
||||||
---@field [2] number
|
---@field [2] number
|
||||||
|
|
||||||
---@alias canola.WindowDimension number|canola.WindowDimensionDualConstraint
|
---@alias oil.WindowDimension number|oil.WindowDimensionDualConstraint
|
||||||
|
|
||||||
---@class (exact) canola.WindowConfig
|
---@class (exact) oil.WindowConfig
|
||||||
---@field max_width canola.WindowDimension
|
---@field max_width oil.WindowDimension
|
||||||
---@field min_width canola.WindowDimension
|
---@field min_width oil.WindowDimension
|
||||||
---@field width? number
|
---@field width? number
|
||||||
---@field max_height canola.WindowDimension
|
---@field max_height oil.WindowDimension
|
||||||
---@field min_height canola.WindowDimension
|
---@field min_height oil.WindowDimension
|
||||||
---@field height? number
|
---@field height? number
|
||||||
---@field border string|string[]
|
---@field border string|string[]
|
||||||
---@field win_options table<string, any>
|
---@field win_options table<string, any>
|
||||||
|
|
||||||
---@class (exact) canola.SetupWindowConfig
|
---@class (exact) oil.SetupWindowConfig
|
||||||
---@field max_width? canola.WindowDimension Width dimensions can be integers or a float between 0 and 1 (e.g. 0.4 for 40%). Can be a single value or a list of mixed integer/float types. max_width = {100, 0.8} means "the lesser of 100 columns or 80% of total"
|
---@field max_width? oil.WindowDimension Width dimensions can be integers or a float between 0 and 1 (e.g. 0.4 for 40%). Can be a single value or a list of mixed integer/float types. max_width = {100, 0.8} means "the lesser of 100 columns or 80% of total"
|
||||||
---@field min_width? canola.WindowDimension Width dimensions can be integers or a float between 0 and 1 (e.g. 0.4 for 40%). Can be a single value or a list of mixed integer/float types. min_width = {40, 0.4} means "the greater of 40 columns or 40% of total"
|
---@field min_width? oil.WindowDimension Width dimensions can be integers or a float between 0 and 1 (e.g. 0.4 for 40%). Can be a single value or a list of mixed integer/float types. min_width = {40, 0.4} means "the greater of 40 columns or 40% of total"
|
||||||
---@field width? number Define an integer/float for the exact width of the preview window
|
---@field width? number Define an integer/float for the exact width of the preview window
|
||||||
---@field max_height? canola.WindowDimension Height dimensions can be integers or a float between 0 and 1 (e.g. 0.4 for 40%). Can be a single value or a list of mixed integer/float types. max_height = {80, 0.9} means "the lesser of 80 columns or 90% of total"
|
---@field max_height? oil.WindowDimension Height dimensions can be integers or a float between 0 and 1 (e.g. 0.4 for 40%). Can be a single value or a list of mixed integer/float types. max_height = {80, 0.9} means "the lesser of 80 columns or 90% of total"
|
||||||
---@field min_height? canola.WindowDimension Height dimensions can be integers or a float between 0 and 1 (e.g. 0.4 for 40%). Can be a single value or a list of mixed integer/float types. min_height = {5, 0.1} means "the greater of 5 columns or 10% of total"
|
---@field min_height? oil.WindowDimension Height dimensions can be integers or a float between 0 and 1 (e.g. 0.4 for 40%). Can be a single value or a list of mixed integer/float types. min_height = {5, 0.1} means "the greater of 5 columns or 10% of total"
|
||||||
---@field height? number Define an integer/float for the exact height of the preview window
|
---@field height? number Define an integer/float for the exact height of the preview window
|
||||||
---@field border? string|string[] Window border
|
---@field border? string|string[] Window border
|
||||||
---@field win_options? table<string, any>
|
---@field win_options? table<string, any>
|
||||||
|
|
||||||
---@alias canola.PreviewMethod
|
---@alias oil.PreviewMethod
|
||||||
---| '"load"' # Load the previewed file into a buffer
|
---| '"load"' # Load the previewed file into a buffer
|
||||||
---| '"scratch"' # Put the text into a scratch buffer to avoid LSP attaching
|
---| '"scratch"' # Put the text into a scratch buffer to avoid LSP attaching
|
||||||
---| '"fast_scratch"' # Put only the visible text into a scratch buffer
|
---| '"fast_scratch"' # Put only the visible text into a scratch buffer
|
||||||
|
|
||||||
---@class (exact) canola.PreviewWindowConfig
|
---@class (exact) oil.PreviewWindowConfig
|
||||||
---@field update_on_cursor_moved boolean
|
---@field update_on_cursor_moved boolean
|
||||||
---@field preview_method canola.PreviewMethod
|
---@field preview_method oil.PreviewMethod
|
||||||
---@field disable_preview fun(filename: string): boolean
|
---@field disable_preview fun(filename: string): boolean
|
||||||
---@field max_file_size number Maximum file size (in MB) to preview. Files larger than this will show a placeholder.
|
|
||||||
---@field win_options table<string, any>
|
---@field win_options table<string, any>
|
||||||
|
|
||||||
---@class (exact) canola.ConfirmationWindowConfig : canola.WindowConfig
|
---@class (exact) oil.ConfirmationWindowConfig : oil.WindowConfig
|
||||||
|
|
||||||
---@class (exact) canola.SetupPreviewWindowConfig
|
---@class (exact) oil.SetupPreviewWindowConfig
|
||||||
---@field update_on_cursor_moved? boolean Whether the preview window is automatically updated when the cursor is moved
|
---@field update_on_cursor_moved? boolean Whether the preview window is automatically updated when the cursor is moved
|
||||||
---@field disable_preview? fun(filename: string): boolean A function that returns true to disable preview on a file e.g. to avoid lag
|
---@field disable_preview? fun(filename: string): boolean A function that returns true to disable preview on a file e.g. to avoid lag
|
||||||
---@field max_file_size? number Maximum file size in MB to show in preview. Files exceeding this will not be loaded (:help canola.preview_win). Set to nil to disable the limit.
|
---@field preview_method? oil.PreviewMethod How to open the preview window
|
||||||
---@field preview_method? canola.PreviewMethod How to open the preview window
|
|
||||||
---@field win_options? table<string, any> Window-local options to use for preview window buffers
|
---@field win_options? table<string, any> Window-local options to use for preview window buffers
|
||||||
|
|
||||||
---@class (exact) canola.SetupConfirmationWindowConfig : canola.SetupWindowConfig
|
---@class (exact) oil.SetupConfirmationWindowConfig : oil.SetupWindowConfig
|
||||||
|
|
||||||
---@class (exact) canola.ProgressWindowConfig : canola.WindowConfig
|
---@class (exact) oil.ProgressWindowConfig : oil.WindowConfig
|
||||||
---@field minimized_border string|string[]
|
---@field minimized_border string|string[]
|
||||||
|
|
||||||
---@class (exact) canola.SetupProgressWindowConfig : canola.SetupWindowConfig
|
---@class (exact) oil.SetupProgressWindowConfig : oil.SetupWindowConfig
|
||||||
---@field minimized_border? string|string[] The border for the minimized progress window
|
---@field minimized_border? string|string[] The border for the minimized progress window
|
||||||
|
|
||||||
---@class (exact) canola.FloatWindowConfig
|
---@class (exact) oil.FloatWindowConfig
|
||||||
---@field padding integer
|
---@field padding integer
|
||||||
---@field max_width integer
|
---@field max_width integer
|
||||||
---@field max_height integer
|
---@field max_height integer
|
||||||
|
|
@ -405,7 +389,7 @@ local M = {}
|
||||||
---@field preview_split "auto"|"left"|"right"|"above"|"below"
|
---@field preview_split "auto"|"left"|"right"|"above"|"below"
|
||||||
---@field override fun(conf: table): table
|
---@field override fun(conf: table): table
|
||||||
|
|
||||||
---@class (exact) canola.SetupFloatWindowConfig
|
---@class (exact) oil.SetupFloatWindowConfig
|
||||||
---@field padding? integer
|
---@field padding? integer
|
||||||
---@field max_width? integer
|
---@field max_width? integer
|
||||||
---@field max_height? integer
|
---@field max_height? integer
|
||||||
|
|
@ -415,16 +399,16 @@ local M = {}
|
||||||
---@field preview_split? "auto"|"left"|"right"|"above"|"below" Direction that the preview command will split the window
|
---@field preview_split? "auto"|"left"|"right"|"above"|"below" Direction that the preview command will split the window
|
||||||
---@field override? fun(conf: table): table
|
---@field override? fun(conf: table): table
|
||||||
|
|
||||||
---@class (exact) canola.SimpleWindowConfig
|
---@class (exact) oil.SimpleWindowConfig
|
||||||
---@field border string|string[]
|
---@field border string|string[]
|
||||||
|
|
||||||
---@class (exact) canola.SetupSimpleWindowConfig
|
---@class (exact) oil.SetupSimpleWindowConfig
|
||||||
---@field border? string|string[] Window border
|
---@field border? string|string[] Window border
|
||||||
|
|
||||||
M.setup = function(opts)
|
M.setup = function(opts)
|
||||||
opts = opts or vim.g.canola or {}
|
opts = opts or vim.g.oil or {}
|
||||||
|
|
||||||
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 {}
|
||||||
elseif opts.keymaps then
|
elseif opts.keymaps then
|
||||||
|
|
@ -445,19 +429,19 @@ M.setup = function(opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Backwards compatibility for old versions that don't support winborder
|
-- Backwards compatibility for old versions that don't support winborder
|
||||||
if vim.fn.has('nvim-0.11') == 0 then
|
if vim.fn.has("nvim-0.11") == 0 then
|
||||||
new_conf = vim.tbl_deep_extend('keep', new_conf, {
|
new_conf = vim.tbl_deep_extend("keep", new_conf, {
|
||||||
float = { border = 'rounded' },
|
float = { border = "rounded" },
|
||||||
confirmation = { border = 'rounded' },
|
confirmation = { border = "rounded" },
|
||||||
progress = { border = 'rounded' },
|
progress = { border = "rounded" },
|
||||||
ssh = { border = 'rounded' },
|
ssh = { border = "rounded" },
|
||||||
keymaps_help = { border = 'rounded' },
|
keymaps_help = { border = "rounded" },
|
||||||
})
|
})
|
||||||
end
|
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)
|
||||||
end
|
end
|
||||||
-- Backwards compatibility. We renamed the 'preview' config to 'preview_win'
|
-- Backwards compatibility. We renamed the 'preview' config to 'preview_win'
|
||||||
if opts.preview and opts.preview.update_on_cursor_moved ~= nil then
|
if opts.preview and opts.preview.update_on_cursor_moved ~= nil then
|
||||||
|
|
@ -468,7 +452,7 @@ M.setup = function(opts)
|
||||||
new_conf.lsp_file_methods.autosave_changes = new_conf.lsp_rename_autosave
|
new_conf.lsp_file_methods.autosave_changes = new_conf.lsp_rename_autosave
|
||||||
new_conf.lsp_rename_autosave = nil
|
new_conf.lsp_rename_autosave = nil
|
||||||
vim.notify_once(
|
vim.notify_once(
|
||||||
'canola config value lsp_rename_autosave has moved to lsp_file_methods.autosave_changes.\nCompatibility will be removed on 2024-09-01.',
|
"oil config value lsp_rename_autosave has moved to lsp_file_methods.autosave_changes.\nCompatibility will be removed on 2024-09-01.",
|
||||||
vim.log.levels.WARN
|
vim.log.levels.WARN
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
@ -490,15 +474,15 @@ M.setup = function(opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param scheme nil|string
|
---@param scheme nil|string
|
||||||
---@return nil|canola.Adapter
|
---@return nil|oil.Adapter
|
||||||
M.get_adapter_by_scheme = function(scheme)
|
M.get_adapter_by_scheme = function(scheme)
|
||||||
if not scheme then
|
if not scheme then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
if not vim.endswith(scheme, '://') then
|
if not vim.endswith(scheme, "://") then
|
||||||
local pieces = vim.split(scheme, '://', { plain = true })
|
local pieces = vim.split(scheme, "://", { plain = true })
|
||||||
if #pieces <= 2 then
|
if #pieces <= 2 then
|
||||||
scheme = pieces[1] .. '://'
|
scheme = pieces[1] .. "://"
|
||||||
else
|
else
|
||||||
error(string.format("Malformed url: '%s'", scheme))
|
error(string.format("Malformed url: '%s'", scheme))
|
||||||
end
|
end
|
||||||
|
|
@ -510,7 +494,7 @@ M.get_adapter_by_scheme = function(scheme)
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
local ok
|
local ok
|
||||||
ok, adapter = pcall(require, string.format('canola.adapters.%s', name))
|
ok, adapter = pcall(require, string.format("oil.adapters.%s", name))
|
||||||
if ok then
|
if ok then
|
||||||
adapter.name = name
|
adapter.name = name
|
||||||
M._adapter_by_scheme[scheme] = adapter
|
M._adapter_by_scheme[scheme] = adapter
|
||||||
|
|
@ -2,9 +2,9 @@ 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 canola.InternalEntry {[1]: integer, [2]: string, [3]: canola.EntryType, [4]: nil|table}
|
---@alias oil.InternalEntry {[1]: integer, [2]: string, [3]: oil.EntryType, [4]: nil|table}
|
||||||
|
|
||||||
-- Indexes into canola.InternalEntry
|
-- Indexes into oil.InternalEntry
|
||||||
M.FIELD_ID = 1
|
M.FIELD_ID = 1
|
||||||
M.FIELD_NAME = 2
|
M.FIELD_NAME = 2
|
||||||
M.FIELD_TYPE = 3
|
M.FIELD_TYPE = 3
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
local log = require('canola.log')
|
local log = require("oil.log")
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local uv = vim.uv or vim.loop
|
local uv = vim.uv or vim.loop
|
||||||
|
|
||||||
---@type boolean
|
---@type boolean
|
||||||
M.is_windows = uv.os_uname().version:match('Windows')
|
M.is_windows = uv.os_uname().version:match("Windows")
|
||||||
|
|
||||||
M.is_mac = uv.os_uname().sysname == 'Darwin'
|
M.is_mac = uv.os_uname().sysname == "Darwin"
|
||||||
|
|
||||||
M.is_linux = not M.is_windows and not M.is_mac
|
M.is_linux = not M.is_windows and not M.is_mac
|
||||||
|
|
||||||
---@type string
|
---@type string
|
||||||
M.sep = M.is_windows and '\\' or '/'
|
M.sep = M.is_windows and "\\" or "/"
|
||||||
|
|
||||||
---@param ... string
|
---@param ... string
|
||||||
M.join = function(...)
|
M.join = function(...)
|
||||||
|
|
@ -23,15 +23,15 @@ end
|
||||||
---@return boolean
|
---@return boolean
|
||||||
M.is_absolute = function(dir)
|
M.is_absolute = function(dir)
|
||||||
if M.is_windows then
|
if M.is_windows then
|
||||||
return dir:match('^%a:\\')
|
return dir:match("^%a:\\")
|
||||||
else
|
else
|
||||||
return vim.startswith(dir, '/')
|
return vim.startswith(dir, "/")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
M.abspath = function(path)
|
M.abspath = function(path)
|
||||||
if not M.is_absolute(path) then
|
if not M.is_absolute(path) then
|
||||||
path = vim.fn.fnamemodify(path, ':p')
|
path = vim.fn.fnamemodify(path, ":p")
|
||||||
end
|
end
|
||||||
return path
|
return path
|
||||||
end
|
end
|
||||||
|
|
@ -40,11 +40,11 @@ end
|
||||||
---@param mode? integer File mode in decimal (default 420 = 0644)
|
---@param mode? integer File mode in decimal (default 420 = 0644)
|
||||||
---@param cb fun(err: nil|string)
|
---@param cb fun(err: nil|string)
|
||||||
M.touch = function(path, mode, cb)
|
M.touch = function(path, mode, cb)
|
||||||
if type(mode) == 'function' then
|
if type(mode) == "function" then
|
||||||
cb = mode
|
cb = mode
|
||||||
mode = 420
|
mode = 420
|
||||||
end
|
end
|
||||||
uv.fs_open(path, 'a', mode or 420, function(err, fd)
|
uv.fs_open(path, "a", mode or 420, function(err, fd)
|
||||||
if err then
|
if err then
|
||||||
cb(err)
|
cb(err)
|
||||||
else
|
else
|
||||||
|
|
@ -59,12 +59,12 @@ end
|
||||||
---@param candidate string
|
---@param candidate string
|
||||||
---@return boolean
|
---@return boolean
|
||||||
M.is_subpath = function(root, candidate)
|
M.is_subpath = function(root, candidate)
|
||||||
if candidate == '' then
|
if candidate == "" then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
root = vim.fs.normalize(M.abspath(root))
|
root = vim.fs.normalize(M.abspath(root))
|
||||||
-- Trim trailing "/" from the root
|
-- Trim trailing "/" from the root
|
||||||
if root:find('/', -1) then
|
if root:find("/", -1) then
|
||||||
root = root:sub(1, -2)
|
root = root:sub(1, -2)
|
||||||
end
|
end
|
||||||
candidate = vim.fs.normalize(M.abspath(candidate))
|
candidate = vim.fs.normalize(M.abspath(candidate))
|
||||||
|
|
@ -80,8 +80,8 @@ M.is_subpath = function(root, candidate)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
local candidate_starts_with_sep = candidate:find('/', root:len() + 1, true) == root:len() + 1
|
local candidate_starts_with_sep = candidate:find("/", root:len() + 1, true) == root:len() + 1
|
||||||
local root_ends_with_sep = root:find('/', root:len(), true) == root:len()
|
local root_ends_with_sep = root:find("/", root:len(), true) == root:len()
|
||||||
|
|
||||||
return candidate_starts_with_sep or root_ends_with_sep
|
return candidate_starts_with_sep or root_ends_with_sep
|
||||||
end
|
end
|
||||||
|
|
@ -90,15 +90,15 @@ end
|
||||||
---@return string
|
---@return string
|
||||||
M.posix_to_os_path = function(path)
|
M.posix_to_os_path = function(path)
|
||||||
if M.is_windows then
|
if M.is_windows then
|
||||||
if vim.startswith(path, '/') then
|
if vim.startswith(path, "/") then
|
||||||
local drive = path:match('^/(%a+)')
|
local drive = path:match("^/(%a+)")
|
||||||
if not drive then
|
if not drive then
|
||||||
return path
|
return path
|
||||||
end
|
end
|
||||||
local rem = path:sub(drive:len() + 2)
|
local rem = path:sub(drive:len() + 2)
|
||||||
return string.format('%s:%s', drive, rem:gsub('/', '\\'))
|
return string.format("%s:%s", drive, rem:gsub("/", "\\"))
|
||||||
else
|
else
|
||||||
local newpath = path:gsub('/', '\\')
|
local newpath = path:gsub("/", "\\")
|
||||||
return newpath
|
return newpath
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
|
@ -111,10 +111,10 @@ end
|
||||||
M.os_to_posix_path = function(path)
|
M.os_to_posix_path = function(path)
|
||||||
if M.is_windows then
|
if M.is_windows then
|
||||||
if M.is_absolute(path) then
|
if M.is_absolute(path) then
|
||||||
local drive, rem = path:match('^([^:]+):\\(.*)$')
|
local drive, rem = path:match("^([^:]+):\\(.*)$")
|
||||||
return string.format('/%s/%s', drive:upper(), rem:gsub('\\', '/'))
|
return string.format("/%s/%s", drive:upper(), rem:gsub("\\", "/"))
|
||||||
else
|
else
|
||||||
local newpath = path:gsub('\\', '/')
|
local newpath = path:gsub("\\", "/")
|
||||||
return newpath
|
return newpath
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
|
@ -135,16 +135,16 @@ M.shorten_path = function(path, relative_to)
|
||||||
if M.is_subpath(relative_to, path) then
|
if M.is_subpath(relative_to, path) then
|
||||||
local idx = relative_to:len() + 1
|
local idx = relative_to:len() + 1
|
||||||
-- Trim the dividing slash if it's not included in relative_to
|
-- Trim the dividing slash if it's not included in relative_to
|
||||||
if not vim.endswith(relative_to, '/') and not vim.endswith(relative_to, '\\') then
|
if not vim.endswith(relative_to, "/") and not vim.endswith(relative_to, "\\") then
|
||||||
idx = idx + 1
|
idx = idx + 1
|
||||||
end
|
end
|
||||||
relpath = path:sub(idx)
|
relpath = path:sub(idx)
|
||||||
if relpath == '' then
|
if relpath == "" then
|
||||||
relpath = '.'
|
relpath = "."
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if M.is_subpath(home_dir, path) then
|
if M.is_subpath(home_dir, path) then
|
||||||
local homepath = '~' .. path:sub(home_dir:len() + 1)
|
local homepath = "~" .. path:sub(home_dir:len() + 1)
|
||||||
if not relpath or homepath:len() < relpath:len() then
|
if not relpath or homepath:len() < relpath:len() then
|
||||||
return homepath
|
return homepath
|
||||||
end
|
end
|
||||||
|
|
@ -156,13 +156,13 @@ end
|
||||||
---@param mode? integer
|
---@param mode? integer
|
||||||
M.mkdirp = function(dir, mode)
|
M.mkdirp = function(dir, mode)
|
||||||
mode = mode or 493
|
mode = mode or 493
|
||||||
local mod = ''
|
local mod = ""
|
||||||
local path = dir
|
local path = dir
|
||||||
while vim.fn.isdirectory(path) == 0 do
|
while vim.fn.isdirectory(path) == 0 do
|
||||||
mod = mod .. ':h'
|
mod = mod .. ":h"
|
||||||
path = vim.fn.fnamemodify(dir, mod)
|
path = vim.fn.fnamemodify(dir, mod)
|
||||||
end
|
end
|
||||||
while mod ~= '' do
|
while mod ~= "" do
|
||||||
mod = mod:sub(3)
|
mod = mod:sub(3)
|
||||||
path = vim.fn.fnamemodify(dir, mod)
|
path = vim.fn.fnamemodify(dir, mod)
|
||||||
uv.fs_mkdir(path, mode)
|
uv.fs_mkdir(path, mode)
|
||||||
|
|
@ -170,7 +170,7 @@ M.mkdirp = function(dir, mode)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param dir string
|
---@param dir string
|
||||||
---@param cb fun(err: nil|string, entries: nil|{type: canola.EntryType, name: string})
|
---@param cb fun(err: nil|string, entries: nil|{type: oil.EntryType, name: string})
|
||||||
M.listdir = function(dir, cb)
|
M.listdir = function(dir, cb)
|
||||||
---@diagnostic disable-next-line: param-type-mismatch, discard-returns
|
---@diagnostic disable-next-line: param-type-mismatch, discard-returns
|
||||||
uv.fs_opendir(dir, function(open_err, fd)
|
uv.fs_opendir(dir, function(open_err, fd)
|
||||||
|
|
@ -205,11 +205,11 @@ M.listdir = function(dir, cb)
|
||||||
end, 10000)
|
end, 10000)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param entry_type canola.EntryType
|
---@param entry_type oil.EntryType
|
||||||
---@param path string
|
---@param path string
|
||||||
---@param cb fun(err: nil|string)
|
---@param cb fun(err: nil|string)
|
||||||
M.recursive_delete = function(entry_type, path, cb)
|
M.recursive_delete = function(entry_type, path, cb)
|
||||||
if entry_type ~= 'directory' then
|
if entry_type ~= "directory" then
|
||||||
return uv.fs_unlink(path, cb)
|
return uv.fs_unlink(path, cb)
|
||||||
end
|
end
|
||||||
---@diagnostic disable-next-line: param-type-mismatch, discard-returns
|
---@diagnostic disable-next-line: param-type-mismatch, discard-returns
|
||||||
|
|
@ -271,13 +271,13 @@ local move_undofile = vim.schedule_wrap(function(src_path, dest_path, copy)
|
||||||
if copy then
|
if copy then
|
||||||
uv.fs_copyfile(src_path, dest_path, function(err)
|
uv.fs_copyfile(src_path, dest_path, function(err)
|
||||||
if err then
|
if err then
|
||||||
log.warn('Error copying undofile %s: %s', undofile, err)
|
log.warn("Error copying undofile %s: %s", undofile, err)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
else
|
else
|
||||||
uv.fs_rename(undofile, dest_undofile, function(err)
|
uv.fs_rename(undofile, dest_undofile, function(err)
|
||||||
if err then
|
if err then
|
||||||
log.warn('Error moving undofile %s: %s', undofile, err)
|
log.warn("Error moving undofile %s: %s", undofile, err)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
@ -285,12 +285,12 @@ local move_undofile = vim.schedule_wrap(function(src_path, dest_path, copy)
|
||||||
)
|
)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
---@param entry_type canola.EntryType
|
---@param entry_type oil.EntryType
|
||||||
---@param src_path string
|
---@param src_path string
|
||||||
---@param dest_path string
|
---@param dest_path string
|
||||||
---@param cb fun(err: nil|string)
|
---@param cb fun(err: nil|string)
|
||||||
M.recursive_copy = function(entry_type, src_path, dest_path, cb)
|
M.recursive_copy = function(entry_type, src_path, dest_path, cb)
|
||||||
if entry_type == 'link' then
|
if entry_type == "link" then
|
||||||
uv.fs_readlink(src_path, function(link_err, link)
|
uv.fs_readlink(src_path, function(link_err, link)
|
||||||
if link_err then
|
if link_err then
|
||||||
return cb(link_err)
|
return cb(link_err)
|
||||||
|
|
@ -300,7 +300,7 @@ M.recursive_copy = function(entry_type, src_path, dest_path, cb)
|
||||||
end)
|
end)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
if entry_type ~= 'directory' then
|
if entry_type ~= "directory" then
|
||||||
uv.fs_copyfile(src_path, dest_path, { excl = true }, cb)
|
uv.fs_copyfile(src_path, dest_path, { excl = true }, cb)
|
||||||
move_undofile(src_path, dest_path, true)
|
move_undofile(src_path, dest_path, true)
|
||||||
return
|
return
|
||||||
|
|
@ -357,7 +357,7 @@ M.recursive_copy = function(entry_type, src_path, dest_path, cb)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param entry_type canola.EntryType
|
---@param entry_type oil.EntryType
|
||||||
---@param src_path string
|
---@param src_path string
|
||||||
---@param dest_path string
|
---@param dest_path string
|
||||||
---@param cb fun(err: nil|string)
|
---@param cb fun(err: nil|string)
|
||||||
|
|
@ -374,7 +374,7 @@ M.recursive_move = function(entry_type, src_path, dest_path, cb)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
else
|
else
|
||||||
if entry_type ~= 'directory' then
|
if entry_type ~= "directory" then
|
||||||
move_undofile(src_path, dest_path, false)
|
move_undofile(src_path, dest_path, false)
|
||||||
end
|
end
|
||||||
cb()
|
cb()
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
-- integration with git operations
|
-- integration with git operations
|
||||||
local fs = require('canola.fs')
|
local fs = require("oil.fs")
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
---@param path string
|
---@param path string
|
||||||
---@return string|nil
|
---@return string|nil
|
||||||
M.get_root = function(path)
|
M.get_root = function(path)
|
||||||
local git_dir = vim.fs.find('.git', { upward = true, path = path })[1]
|
local git_dir = vim.fs.find(".git", { upward = true, path = path })[1]
|
||||||
if git_dir then
|
if git_dir then
|
||||||
return vim.fs.dirname(git_dir)
|
return vim.fs.dirname(git_dir)
|
||||||
else
|
else
|
||||||
|
|
@ -22,16 +22,16 @@ M.add = function(path, cb)
|
||||||
return cb()
|
return cb()
|
||||||
end
|
end
|
||||||
|
|
||||||
local stderr = ''
|
local stderr = ""
|
||||||
local jid = vim.fn.jobstart({ 'git', 'add', path }, {
|
local jid = vim.fn.jobstart({ "git", "add", path }, {
|
||||||
cwd = root,
|
cwd = root,
|
||||||
stderr_buffered = true,
|
stderr_buffered = true,
|
||||||
on_stderr = function(_, data)
|
on_stderr = function(_, data)
|
||||||
stderr = table.concat(data, '\n')
|
stderr = table.concat(data, "\n")
|
||||||
end,
|
end,
|
||||||
on_exit = function(_, code)
|
on_exit = function(_, code)
|
||||||
if code ~= 0 then
|
if code ~= 0 then
|
||||||
cb('Error in git add: ' .. stderr)
|
cb("Error in git add: " .. stderr)
|
||||||
else
|
else
|
||||||
cb()
|
cb()
|
||||||
end
|
end
|
||||||
|
|
@ -50,12 +50,12 @@ M.rm = function(path, cb)
|
||||||
return cb()
|
return cb()
|
||||||
end
|
end
|
||||||
|
|
||||||
local stderr = ''
|
local stderr = ""
|
||||||
local jid = vim.fn.jobstart({ 'git', 'rm', '-r', path }, {
|
local jid = vim.fn.jobstart({ "git", "rm", "-r", path }, {
|
||||||
cwd = root,
|
cwd = root,
|
||||||
stderr_buffered = true,
|
stderr_buffered = true,
|
||||||
on_stderr = function(_, data)
|
on_stderr = function(_, data)
|
||||||
stderr = table.concat(data, '\n')
|
stderr = table.concat(data, "\n")
|
||||||
end,
|
end,
|
||||||
on_exit = function(_, code)
|
on_exit = function(_, code)
|
||||||
if code ~= 0 then
|
if code ~= 0 then
|
||||||
|
|
@ -63,7 +63,7 @@ M.rm = function(path, cb)
|
||||||
if stderr:match("^fatal: pathspec '.*' did not match any files$") then
|
if stderr:match("^fatal: pathspec '.*' did not match any files$") then
|
||||||
cb()
|
cb()
|
||||||
else
|
else
|
||||||
cb('Error in git rm: ' .. stderr)
|
cb("Error in git rm: " .. stderr)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
cb()
|
cb()
|
||||||
|
|
@ -75,7 +75,7 @@ M.rm = function(path, cb)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param entry_type canola.EntryType
|
---@param entry_type oil.EntryType
|
||||||
---@param src_path string
|
---@param src_path string
|
||||||
---@param dest_path string
|
---@param dest_path string
|
||||||
---@param cb fun(err: nil|string)
|
---@param cb fun(err: nil|string)
|
||||||
|
|
@ -86,23 +86,23 @@ M.mv = function(entry_type, src_path, dest_path, cb)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local stderr = ''
|
local stderr = ""
|
||||||
local jid = vim.fn.jobstart({ 'git', 'mv', src_path, dest_path }, {
|
local jid = vim.fn.jobstart({ "git", "mv", src_path, dest_path }, {
|
||||||
cwd = src_git,
|
cwd = src_git,
|
||||||
stderr_buffered = true,
|
stderr_buffered = true,
|
||||||
on_stderr = function(_, data)
|
on_stderr = function(_, data)
|
||||||
stderr = table.concat(data, '\n')
|
stderr = table.concat(data, "\n")
|
||||||
end,
|
end,
|
||||||
on_exit = function(_, code)
|
on_exit = function(_, code)
|
||||||
if code ~= 0 then
|
if code ~= 0 then
|
||||||
stderr = vim.trim(stderr)
|
stderr = vim.trim(stderr)
|
||||||
if
|
if
|
||||||
stderr:match('^fatal: not under version control')
|
stderr:match("^fatal: not under version control")
|
||||||
or stderr:match('^fatal: source directory is empty')
|
or stderr:match("^fatal: source directory is empty")
|
||||||
then
|
then
|
||||||
fs.recursive_move(entry_type, src_path, dest_path, cb)
|
fs.recursive_move(entry_type, src_path, dest_path, cb)
|
||||||
else
|
else
|
||||||
cb('Error in git mv: ' .. stderr)
|
cb("Error in git mv: " .. stderr)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
cb()
|
cb()
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,7 +1,7 @@
|
||||||
local actions = require('canola.actions')
|
local actions = require("oil.actions")
|
||||||
local config = require('canola.config')
|
local config = require("oil.config")
|
||||||
local layout = require('canola.layout')
|
local layout = require("oil.layout")
|
||||||
local util = require('canola.util')
|
local util = require("oil.util")
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
---@param rhs string|table|fun()
|
---@param rhs string|table|fun()
|
||||||
|
|
@ -9,14 +9,14 @@ local M = {}
|
||||||
---@return table opts
|
---@return table opts
|
||||||
---@return string|nil mode
|
---@return string|nil mode
|
||||||
local function resolve(rhs)
|
local function resolve(rhs)
|
||||||
if type(rhs) == 'string' and vim.startswith(rhs, 'actions.') then
|
if type(rhs) == "string" and vim.startswith(rhs, "actions.") then
|
||||||
local action_name = vim.split(rhs, '.', { plain = true })[2]
|
local action_name = vim.split(rhs, ".", { plain = true })[2]
|
||||||
local action = actions[action_name]
|
local action = actions[action_name]
|
||||||
if not action then
|
if not action then
|
||||||
vim.notify('[canola.nvim] Unknown action name: ' .. action_name, vim.log.levels.ERROR)
|
vim.notify("[oil.nvim] Unknown action name: " .. action_name, vim.log.levels.ERROR)
|
||||||
end
|
end
|
||||||
return resolve(action)
|
return resolve(action)
|
||||||
elseif type(rhs) == 'table' then
|
elseif type(rhs) == "table" then
|
||||||
local opts = vim.deepcopy(rhs)
|
local opts = vim.deepcopy(rhs)
|
||||||
-- We support passing in a `callback` key, or using the 1 index as the rhs of the keymap
|
-- We support passing in a `callback` key, or using the 1 index as the rhs of the keymap
|
||||||
local callback, parent_opts = resolve(opts.callback or opts[1])
|
local callback, parent_opts = resolve(opts.callback or opts[1])
|
||||||
|
|
@ -25,17 +25,17 @@ local function resolve(rhs)
|
||||||
if parent_opts.desc and not opts.desc then
|
if parent_opts.desc and not opts.desc then
|
||||||
if opts.opts then
|
if opts.opts then
|
||||||
opts.desc =
|
opts.desc =
|
||||||
string.format('%s %s', parent_opts.desc, vim.inspect(opts.opts):gsub('%s+', ' '))
|
string.format("%s %s", parent_opts.desc, vim.inspect(opts.opts):gsub("%s+", " "))
|
||||||
else
|
else
|
||||||
opts.desc = parent_opts.desc
|
opts.desc = parent_opts.desc
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local mode = opts.mode
|
local mode = opts.mode
|
||||||
if type(rhs.callback) == 'string' then
|
if type(rhs.callback) == "string" then
|
||||||
local action_opts, action_mode
|
local action_opts, action_mode
|
||||||
callback, action_opts, action_mode = resolve(rhs.callback)
|
callback, action_opts, action_mode = resolve(rhs.callback)
|
||||||
opts = vim.tbl_extend('keep', opts, action_opts)
|
opts = vim.tbl_extend("keep", opts, action_opts)
|
||||||
mode = mode or action_mode
|
mode = mode or action_mode
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -46,7 +46,7 @@ local function resolve(rhs)
|
||||||
opts.deprecated = nil
|
opts.deprecated = nil
|
||||||
opts.parameters = nil
|
opts.parameters = nil
|
||||||
|
|
||||||
if opts.opts and type(callback) == 'function' then
|
if opts.opts and type(callback) == "function" then
|
||||||
local callback_args = opts.opts
|
local callback_args = opts.opts
|
||||||
opts.opts = nil
|
opts.opts = nil
|
||||||
local orig_callback = callback
|
local orig_callback = callback
|
||||||
|
|
@ -68,7 +68,7 @@ M.set_keymaps = function(keymaps, bufnr)
|
||||||
for k, v in pairs(keymaps) do
|
for k, v in pairs(keymaps) do
|
||||||
local rhs, opts, mode = resolve(v)
|
local rhs, opts, mode = resolve(v)
|
||||||
if rhs then
|
if rhs then
|
||||||
vim.keymap.set(mode or '', k, rhs, vim.tbl_extend('keep', { buffer = bufnr }, opts))
|
vim.keymap.set(mode or "", k, rhs, vim.tbl_extend("keep", { buffer = bufnr }, opts))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -95,9 +95,9 @@ M.show_help = function(keymaps)
|
||||||
local all_lhs = lhs_to_all_lhs[k]
|
local all_lhs = lhs_to_all_lhs[k]
|
||||||
if all_lhs then
|
if all_lhs then
|
||||||
local _, opts = resolve(rhs)
|
local _, opts = resolve(rhs)
|
||||||
local keystr = table.concat(all_lhs, '/')
|
local keystr = table.concat(all_lhs, "/")
|
||||||
max_lhs = math.max(max_lhs, vim.api.nvim_strwidth(keystr))
|
max_lhs = math.max(max_lhs, vim.api.nvim_strwidth(keystr))
|
||||||
table.insert(keymap_entries, { str = keystr, all_lhs = all_lhs, desc = opts.desc or '' })
|
table.insert(keymap_entries, { str = keystr, all_lhs = all_lhs, desc = opts.desc or "" })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
table.sort(keymap_entries, function(a, b)
|
table.sort(keymap_entries, function(a, b)
|
||||||
|
|
@ -108,20 +108,20 @@ 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.pad_align(entry.str, max_lhs, 'left'), 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
|
||||||
for _, key in ipairs(entry.all_lhs) do
|
for _, key in ipairs(entry.all_lhs) do
|
||||||
local keywidth = vim.api.nvim_strwidth(key)
|
local keywidth = vim.api.nvim_strwidth(key)
|
||||||
table.insert(highlights, { 'Special', #lines, start, start + keywidth })
|
table.insert(highlights, { "Special", #lines, start, start + keywidth })
|
||||||
start = start + keywidth + 1
|
start = start + keywidth + 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, lines)
|
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, lines)
|
||||||
local ns = vim.api.nvim_create_namespace('Canola')
|
local ns = vim.api.nvim_create_namespace("Oil")
|
||||||
for _, hl in ipairs(highlights) do
|
for _, hl in ipairs(highlights) do
|
||||||
local hl_group, lnum, start_col, end_col = unpack(hl)
|
local hl_group, lnum, start_col, end_col = unpack(hl)
|
||||||
vim.api.nvim_buf_set_extmark(bufnr, ns, lnum - 1, start_col, {
|
vim.api.nvim_buf_set_extmark(bufnr, ns, lnum - 1, start_col, {
|
||||||
|
|
@ -129,21 +129,21 @@ M.show_help = function(keymaps)
|
||||||
hl_group = hl_group,
|
hl_group = hl_group,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
vim.keymap.set('n', 'q', '<cmd>close<CR>', { buffer = bufnr })
|
vim.keymap.set("n", "q", "<cmd>close<CR>", { buffer = bufnr })
|
||||||
vim.keymap.set('n', '<c-c>', '<cmd>close<CR>', { buffer = bufnr })
|
vim.keymap.set("n", "<c-c>", "<cmd>close<CR>", { buffer = bufnr })
|
||||||
vim.bo[bufnr].modifiable = false
|
vim.bo[bufnr].modifiable = false
|
||||||
vim.bo[bufnr].bufhidden = 'wipe'
|
vim.bo[bufnr].bufhidden = "wipe"
|
||||||
|
|
||||||
local editor_width = vim.o.columns
|
local editor_width = vim.o.columns
|
||||||
local editor_height = layout.get_editor_height()
|
local editor_height = layout.get_editor_height()
|
||||||
local winid = vim.api.nvim_open_win(bufnr, true, {
|
local winid = vim.api.nvim_open_win(bufnr, true, {
|
||||||
relative = 'editor',
|
relative = "editor",
|
||||||
row = math.max(0, (editor_height - #lines) / 2),
|
row = math.max(0, (editor_height - #lines) / 2),
|
||||||
col = math.max(0, (editor_width - max_line - 1) / 2),
|
col = math.max(0, (editor_width - max_line - 1) / 2),
|
||||||
width = math.min(editor_width, max_line + 1),
|
width = math.min(editor_width, max_line + 1),
|
||||||
height = math.min(editor_height, #lines),
|
height = math.min(editor_height, #lines),
|
||||||
zindex = 150,
|
zindex = 150,
|
||||||
style = 'minimal',
|
style = "minimal",
|
||||||
border = config.keymaps_help.border,
|
border = config.keymaps_help.border,
|
||||||
})
|
})
|
||||||
local function close()
|
local function close()
|
||||||
|
|
@ -151,13 +151,13 @@ M.show_help = function(keymaps)
|
||||||
vim.api.nvim_win_close(winid, true)
|
vim.api.nvim_win_close(winid, true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
vim.api.nvim_create_autocmd('BufLeave', {
|
vim.api.nvim_create_autocmd("BufLeave", {
|
||||||
callback = close,
|
callback = close,
|
||||||
once = true,
|
once = true,
|
||||||
nested = true,
|
nested = true,
|
||||||
buffer = bufnr,
|
buffer = bufnr,
|
||||||
})
|
})
|
||||||
vim.api.nvim_create_autocmd('WinLeave', {
|
vim.api.nvim_create_autocmd("WinLeave", {
|
||||||
callback = close,
|
callback = close,
|
||||||
once = true,
|
once = true,
|
||||||
nested = true,
|
nested = true,
|
||||||
|
|
@ -43,7 +43,7 @@ local function calc_list(values, max_value, aggregator, limit)
|
||||||
local ret = limit
|
local ret = limit
|
||||||
if not max_value or not values then
|
if not max_value or not values then
|
||||||
return nil
|
return nil
|
||||||
elseif type(values) == 'table' then
|
elseif type(values) == "table" then
|
||||||
for _, v in ipairs(values) do
|
for _, v in ipairs(values) do
|
||||||
ret = aggregator(ret, calc_float(v, max_value))
|
ret = aggregator(ret, calc_float(v, max_value))
|
||||||
end
|
end
|
||||||
|
|
@ -98,7 +98,7 @@ M.calculate_height = function(desired_height, opts)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class (exact) canola.WinLayout
|
---@class (exact) oil.WinLayout
|
||||||
---@field width integer
|
---@field width integer
|
||||||
---@field height integer
|
---@field height integer
|
||||||
---@field row integer
|
---@field row integer
|
||||||
|
|
@ -106,12 +106,12 @@ end
|
||||||
|
|
||||||
---@return vim.api.keyset.win_config
|
---@return vim.api.keyset.win_config
|
||||||
M.get_fullscreen_win_opts = function()
|
M.get_fullscreen_win_opts = function()
|
||||||
local config = require('canola.config')
|
local config = require("oil.config")
|
||||||
|
|
||||||
local total_width = M.get_editor_width()
|
local total_width = M.get_editor_width()
|
||||||
local total_height = M.get_editor_height()
|
local total_height = M.get_editor_height()
|
||||||
local width = total_width - 2 * config.float.padding
|
local width = total_width - 2 * config.float.padding
|
||||||
if config.float.border ~= 'none' then
|
if config.float.border ~= "none" then
|
||||||
width = width - 2 -- The border consumes 1 col on each side
|
width = width - 2 -- The border consumes 1 col on each side
|
||||||
end
|
end
|
||||||
if config.float.max_width > 0 then
|
if config.float.max_width > 0 then
|
||||||
|
|
@ -127,7 +127,7 @@ M.get_fullscreen_win_opts = function()
|
||||||
local col = math.floor((total_width - width) / 2) - 1 -- adjust for border width
|
local col = math.floor((total_width - width) / 2) - 1 -- adjust for border width
|
||||||
|
|
||||||
local win_opts = {
|
local win_opts = {
|
||||||
relative = 'editor',
|
relative = "editor",
|
||||||
width = width,
|
width = width,
|
||||||
height = height,
|
height = height,
|
||||||
row = row,
|
row = row,
|
||||||
|
|
@ -141,29 +141,29 @@ end
|
||||||
---@param winid integer
|
---@param winid integer
|
||||||
---@param direction "above"|"below"|"left"|"right"|"auto"
|
---@param direction "above"|"below"|"left"|"right"|"auto"
|
||||||
---@param gap integer
|
---@param gap integer
|
||||||
---@return canola.WinLayout root_dim New dimensions of the original window
|
---@return oil.WinLayout root_dim New dimensions of the original window
|
||||||
---@return canola.WinLayout new_dim New dimensions of the new window
|
---@return oil.WinLayout new_dim New dimensions of the new window
|
||||||
M.split_window = function(winid, direction, gap)
|
M.split_window = function(winid, direction, gap)
|
||||||
if direction == 'auto' then
|
if direction == "auto" then
|
||||||
direction = vim.o.splitright and 'right' or 'left'
|
direction = vim.o.splitright and "right" or "left"
|
||||||
end
|
end
|
||||||
|
|
||||||
local float_config = vim.api.nvim_win_get_config(winid)
|
local float_config = vim.api.nvim_win_get_config(winid)
|
||||||
---@type canola.WinLayout
|
---@type oil.WinLayout
|
||||||
local dim_root = {
|
local dim_root = {
|
||||||
width = float_config.width,
|
width = float_config.width,
|
||||||
height = float_config.height,
|
height = float_config.height,
|
||||||
col = float_config.col,
|
col = float_config.col,
|
||||||
row = float_config.row,
|
row = float_config.row,
|
||||||
}
|
}
|
||||||
if vim.fn.has('nvim-0.10') == 0 then
|
if vim.fn.has("nvim-0.10") == 0 then
|
||||||
-- read https://github.com/neovim/neovim/issues/24430 for more infos.
|
-- read https://github.com/neovim/neovim/issues/24430 for more infos.
|
||||||
dim_root.col = float_config.col[vim.val_idx]
|
dim_root.col = float_config.col[vim.val_idx]
|
||||||
dim_root.row = float_config.row[vim.val_idx]
|
dim_root.row = float_config.row[vim.val_idx]
|
||||||
end
|
end
|
||||||
local dim_new = vim.deepcopy(dim_root)
|
local dim_new = vim.deepcopy(dim_root)
|
||||||
|
|
||||||
if direction == 'left' or direction == 'right' then
|
if direction == "left" or direction == "right" then
|
||||||
dim_new.width = math.floor(float_config.width / 2) - math.ceil(gap / 2)
|
dim_new.width = math.floor(float_config.width / 2) - math.ceil(gap / 2)
|
||||||
dim_root.width = dim_new.width
|
dim_root.width = dim_new.width
|
||||||
else
|
else
|
||||||
|
|
@ -171,13 +171,13 @@ M.split_window = function(winid, direction, gap)
|
||||||
dim_root.height = dim_new.height
|
dim_root.height = dim_new.height
|
||||||
end
|
end
|
||||||
|
|
||||||
if direction == 'left' then
|
if direction == "left" then
|
||||||
dim_root.col = dim_root.col + dim_root.width + gap
|
dim_root.col = dim_root.col + dim_root.width + gap
|
||||||
elseif direction == 'right' then
|
elseif direction == "right" then
|
||||||
dim_new.col = dim_new.col + dim_new.width + gap
|
dim_new.col = dim_new.col + dim_new.width + gap
|
||||||
elseif direction == 'above' then
|
elseif direction == "above" then
|
||||||
dim_root.row = dim_root.row + dim_root.height + gap
|
dim_root.row = dim_root.row + dim_root.height + gap
|
||||||
elseif direction == 'below' then
|
elseif direction == "below" then
|
||||||
dim_new.row = dim_new.row + dim_new.height + gap
|
dim_new.row = dim_new.row + dim_new.height + gap
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
local util = require('canola.util')
|
local util = require("oil.util")
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local timers = {}
|
local timers = {}
|
||||||
|
|
@ -12,14 +12,14 @@ M.is_loading = function(bufnr)
|
||||||
end
|
end
|
||||||
|
|
||||||
local spinners = {
|
local spinners = {
|
||||||
dots = { '⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏' },
|
dots = { "⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏" },
|
||||||
}
|
}
|
||||||
|
|
||||||
---@param name_or_frames string|string[]
|
---@param name_or_frames string|string[]
|
||||||
---@return fun(): string
|
---@return fun(): string
|
||||||
M.get_iter = function(name_or_frames)
|
M.get_iter = function(name_or_frames)
|
||||||
local frames
|
local frames
|
||||||
if type(name_or_frames) == 'string' then
|
if type(name_or_frames) == "string" then
|
||||||
frames = spinners[name_or_frames]
|
frames = spinners[name_or_frames]
|
||||||
if not frames then
|
if not frames then
|
||||||
error(string.format("Unrecognized spinner: '%s'", name_or_frames))
|
error(string.format("Unrecognized spinner: '%s'", name_or_frames))
|
||||||
|
|
@ -35,26 +35,26 @@ M.get_iter = function(name_or_frames)
|
||||||
end
|
end
|
||||||
|
|
||||||
M.get_bar_iter = function(opts)
|
M.get_bar_iter = function(opts)
|
||||||
opts = vim.tbl_deep_extend('keep', opts or {}, {
|
opts = vim.tbl_deep_extend("keep", opts or {}, {
|
||||||
bar_size = 3,
|
bar_size = 3,
|
||||||
width = 20,
|
width = 20,
|
||||||
})
|
})
|
||||||
local i = 0
|
local i = 0
|
||||||
return function()
|
return function()
|
||||||
local chars = { '[' }
|
local chars = { "[" }
|
||||||
for _ = 1, opts.width - 2 do
|
for _ = 1, opts.width - 2 do
|
||||||
table.insert(chars, ' ')
|
table.insert(chars, " ")
|
||||||
end
|
end
|
||||||
table.insert(chars, ']')
|
table.insert(chars, "]")
|
||||||
|
|
||||||
for j = i - opts.bar_size, i do
|
for j = i - opts.bar_size, i do
|
||||||
if j > 1 and j < opts.width then
|
if j > 1 and j < opts.width then
|
||||||
chars[j] = '='
|
chars[j] = "="
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
i = (i + 1) % (opts.width + opts.bar_size)
|
i = (i + 1) % (opts.width + opts.bar_size)
|
||||||
return table.concat(chars, '')
|
return table.concat(chars, "")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -75,7 +75,7 @@ M.set_loading = function(bufnr, is_loading)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local lines =
|
local lines =
|
||||||
{ util.pad_align('Loading', math.floor(width / 2) - 3, 'right'), bar_iter() }
|
{ util.pad_align("Loading", math.floor(width / 2) - 3, "right"), bar_iter() }
|
||||||
util.render_text(bufnr, lines)
|
util.render_text(bufnr, lines)
|
||||||
end)
|
end)
|
||||||
)
|
)
|
||||||
|
|
@ -11,14 +11,14 @@ Log.level = vim.log.levels.WARN
|
||||||
|
|
||||||
---@return string
|
---@return string
|
||||||
Log.get_logfile = function()
|
Log.get_logfile = function()
|
||||||
local fs = require('canola.fs')
|
local fs = require("oil.fs")
|
||||||
|
|
||||||
local ok, stdpath = pcall(vim.fn.stdpath, 'log')
|
local ok, stdpath = pcall(vim.fn.stdpath, "log")
|
||||||
if not ok then
|
if not ok then
|
||||||
stdpath = vim.fn.stdpath('cache')
|
stdpath = vim.fn.stdpath("cache")
|
||||||
end
|
end
|
||||||
assert(type(stdpath) == 'string')
|
assert(type(stdpath) == "string")
|
||||||
return fs.join(stdpath, 'canola.log')
|
return fs.join(stdpath, "oil.log")
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param level integer
|
---@param level integer
|
||||||
|
|
@ -29,19 +29,19 @@ local function format(level, msg, ...)
|
||||||
local args = vim.F.pack_len(...)
|
local args = vim.F.pack_len(...)
|
||||||
for i = 1, args.n do
|
for i = 1, args.n do
|
||||||
local v = args[i]
|
local v = args[i]
|
||||||
if type(v) == 'table' then
|
if type(v) == "table" then
|
||||||
args[i] = vim.inspect(v)
|
args[i] = vim.inspect(v)
|
||||||
elseif v == nil then
|
elseif v == nil then
|
||||||
args[i] = 'nil'
|
args[i] = "nil"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
local ok, text = pcall(string.format, msg, vim.F.unpack_len(args))
|
local ok, text = pcall(string.format, msg, vim.F.unpack_len(args))
|
||||||
-- TODO figure out how to get formatted time inside luv callback
|
-- TODO figure out how to get formatted time inside luv callback
|
||||||
-- local timestr = vim.fn.strftime("%Y-%m-%d %H:%M:%S")
|
-- local timestr = vim.fn.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
local timestr = ''
|
local timestr = ""
|
||||||
if ok then
|
if ok then
|
||||||
local str_level = levels_reverse[level]
|
local str_level = levels_reverse[level]
|
||||||
return string.format('%s[%s] %s', timestr, str_level, text)
|
return string.format("%s[%s] %s", timestr, str_level, text)
|
||||||
else
|
else
|
||||||
return string.format(
|
return string.format(
|
||||||
"%s[ERROR] error formatting log line: '%s' args %s",
|
"%s[ERROR] error formatting log line: '%s' args %s",
|
||||||
|
|
@ -67,22 +67,22 @@ local function initialize()
|
||||||
|
|
||||||
local stat = uv.fs_stat(filepath)
|
local stat = uv.fs_stat(filepath)
|
||||||
if stat and stat.size > 10 * 1024 * 1024 then
|
if stat and stat.size > 10 * 1024 * 1024 then
|
||||||
local backup = filepath .. '.1'
|
local backup = filepath .. ".1"
|
||||||
uv.fs_unlink(backup)
|
uv.fs_unlink(backup)
|
||||||
uv.fs_rename(filepath, backup)
|
uv.fs_rename(filepath, backup)
|
||||||
end
|
end
|
||||||
|
|
||||||
local parent = vim.fs.dirname(filepath)
|
local parent = vim.fs.dirname(filepath)
|
||||||
require('canola.fs').mkdirp(parent)
|
require("oil.fs").mkdirp(parent)
|
||||||
|
|
||||||
local logfile, openerr = io.open(filepath, 'a+')
|
local logfile, openerr = io.open(filepath, "a+")
|
||||||
if not logfile then
|
if not logfile then
|
||||||
local err_msg = string.format('Failed to open canola.nvim log file: %s', openerr)
|
local err_msg = string.format("Failed to open oil.nvim log file: %s", openerr)
|
||||||
vim.notify(err_msg, vim.log.levels.ERROR)
|
vim.notify(err_msg, vim.log.levels.ERROR)
|
||||||
else
|
else
|
||||||
write = function(line)
|
write = function(line)
|
||||||
logfile:write(line)
|
logfile:write(line)
|
||||||
logfile:write('\n')
|
logfile:write("\n")
|
||||||
logfile:flush()
|
logfile:flush()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
local config = require('canola.config')
|
local config = require("oil.config")
|
||||||
local fs = require('canola.fs')
|
local fs = require("oil.fs")
|
||||||
local util = require('canola.util')
|
local util = require("oil.util")
|
||||||
local workspace = require('canola.lsp.workspace')
|
local workspace = require("oil.lsp.workspace")
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
---@param actions canola.Action[]
|
---@param actions oil.Action[]
|
||||||
---@return fun() did_perform Call this function when the file operations have been completed
|
---@return fun() did_perform Call this function when the file operations have been completed
|
||||||
M.will_perform_file_operations = function(actions)
|
M.will_perform_file_operations = function(actions)
|
||||||
local moves = {}
|
local moves = {}
|
||||||
local creates = {}
|
local creates = {}
|
||||||
local deletes = {}
|
local deletes = {}
|
||||||
for _, action in ipairs(actions) do
|
for _, action in ipairs(actions) do
|
||||||
if action.type == 'move' then
|
if action.type == "move" then
|
||||||
local src_scheme, src_path = util.parse_url(action.src_url)
|
local src_scheme, src_path = util.parse_url(action.src_url)
|
||||||
assert(src_path)
|
assert(src_path)
|
||||||
local src_adapter = assert(config.get_adapter_by_scheme(src_scheme))
|
local src_adapter = assert(config.get_adapter_by_scheme(src_scheme))
|
||||||
|
|
@ -20,32 +20,32 @@ M.will_perform_file_operations = function(actions)
|
||||||
local dest_adapter = assert(config.get_adapter_by_scheme(dest_scheme))
|
local dest_adapter = assert(config.get_adapter_by_scheme(dest_scheme))
|
||||||
src_path = fs.posix_to_os_path(src_path)
|
src_path = fs.posix_to_os_path(src_path)
|
||||||
dest_path = fs.posix_to_os_path(assert(dest_path))
|
dest_path = fs.posix_to_os_path(assert(dest_path))
|
||||||
if src_adapter.name == 'files' and dest_adapter.name == 'files' then
|
if src_adapter.name == "files" and dest_adapter.name == "files" then
|
||||||
moves[src_path] = dest_path
|
moves[src_path] = dest_path
|
||||||
elseif src_adapter.name == 'files' then
|
elseif src_adapter.name == "files" then
|
||||||
table.insert(deletes, src_path)
|
table.insert(deletes, src_path)
|
||||||
elseif dest_adapter.name == 'files' then
|
elseif dest_adapter.name == "files" then
|
||||||
table.insert(creates, src_path)
|
table.insert(creates, src_path)
|
||||||
end
|
end
|
||||||
elseif action.type == 'create' then
|
elseif action.type == "create" then
|
||||||
local scheme, path = util.parse_url(action.url)
|
local scheme, path = util.parse_url(action.url)
|
||||||
path = fs.posix_to_os_path(assert(path))
|
path = fs.posix_to_os_path(assert(path))
|
||||||
local adapter = assert(config.get_adapter_by_scheme(scheme))
|
local adapter = assert(config.get_adapter_by_scheme(scheme))
|
||||||
if adapter.name == 'files' then
|
if adapter.name == "files" then
|
||||||
table.insert(creates, path)
|
table.insert(creates, path)
|
||||||
end
|
end
|
||||||
elseif action.type == 'delete' then
|
elseif action.type == "delete" then
|
||||||
local scheme, path = util.parse_url(action.url)
|
local scheme, path = util.parse_url(action.url)
|
||||||
path = fs.posix_to_os_path(assert(path))
|
path = fs.posix_to_os_path(assert(path))
|
||||||
local adapter = assert(config.get_adapter_by_scheme(scheme))
|
local adapter = assert(config.get_adapter_by_scheme(scheme))
|
||||||
if adapter.name == 'files' then
|
if adapter.name == "files" then
|
||||||
table.insert(deletes, path)
|
table.insert(deletes, path)
|
||||||
end
|
end
|
||||||
elseif action.type == 'copy' then
|
elseif action.type == "copy" then
|
||||||
local scheme, path = util.parse_url(action.dest_url)
|
local scheme, path = util.parse_url(action.dest_url)
|
||||||
path = fs.posix_to_os_path(assert(path))
|
path = fs.posix_to_os_path(assert(path))
|
||||||
local adapter = assert(config.get_adapter_by_scheme(scheme))
|
local adapter = assert(config.get_adapter_by_scheme(scheme))
|
||||||
if adapter.name == 'files' then
|
if adapter.name == "files" then
|
||||||
table.insert(creates, path)
|
table.insert(creates, path)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -84,7 +84,7 @@ M.will_perform_file_operations = function(actions)
|
||||||
accum(workspace.will_rename_files(moves, { timeout_ms = timeout_ms }))
|
accum(workspace.will_rename_files(moves, { timeout_ms = timeout_ms }))
|
||||||
if final_err then
|
if final_err then
|
||||||
vim.notify(
|
vim.notify(
|
||||||
string.format('[lsp] file operation error: %s', vim.inspect(final_err)),
|
string.format("[lsp] file operation error: %s", vim.inspect(final_err)),
|
||||||
vim.log.levels.WARN
|
vim.log.levels.WARN
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
@ -102,7 +102,7 @@ M.will_perform_file_operations = function(actions)
|
||||||
local bufnr = vim.uri_to_bufnr(uri)
|
local bufnr = vim.uri_to_bufnr(uri)
|
||||||
local was_open = buf_was_modified[bufnr] ~= nil
|
local was_open = buf_was_modified[bufnr] ~= nil
|
||||||
local was_modified = buf_was_modified[bufnr]
|
local was_modified = buf_was_modified[bufnr]
|
||||||
local should_save = autosave == true or (autosave == 'unmodified' and not was_modified)
|
local should_save = autosave == true or (autosave == "unmodified" and not was_modified)
|
||||||
-- Autosave changed buffers if they were not modified before
|
-- Autosave changed buffers if they were not modified before
|
||||||
if should_save then
|
if should_save then
|
||||||
vim.api.nvim_buf_call(bufnr, function()
|
vim.api.nvim_buf_call(bufnr, function()
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
local fs = require('canola.fs')
|
local fs = require("oil.fs")
|
||||||
local ms = require('vim.lsp.protocol').Methods
|
local ms = require("vim.lsp.protocol").Methods
|
||||||
if vim.fn.has('nvim-0.10') == 0 then
|
if vim.fn.has("nvim-0.10") == 0 then
|
||||||
ms = {
|
ms = {
|
||||||
workspace_willCreateFiles = 'workspace/willCreateFiles',
|
workspace_willCreateFiles = "workspace/willCreateFiles",
|
||||||
workspace_didCreateFiles = 'workspace/didCreateFiles',
|
workspace_didCreateFiles = "workspace/didCreateFiles",
|
||||||
workspace_willDeleteFiles = 'workspace/willDeleteFiles',
|
workspace_willDeleteFiles = "workspace/willDeleteFiles",
|
||||||
workspace_didDeleteFiles = 'workspace/didDeleteFiles',
|
workspace_didDeleteFiles = "workspace/didDeleteFiles",
|
||||||
workspace_willRenameFiles = 'workspace/willRenameFiles',
|
workspace_willRenameFiles = "workspace/willRenameFiles",
|
||||||
workspace_didRenameFiles = 'workspace/didRenameFiles',
|
workspace_didRenameFiles = "workspace/didRenameFiles",
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -16,7 +16,7 @@ local M = {}
|
||||||
---@param method string
|
---@param method string
|
||||||
---@return vim.lsp.Client[]
|
---@return vim.lsp.Client[]
|
||||||
local function get_clients(method)
|
local function get_clients(method)
|
||||||
if vim.fn.has('nvim-0.10') == 1 then
|
if vim.fn.has("nvim-0.10") == 1 then
|
||||||
return vim.lsp.get_clients({ method = method })
|
return vim.lsp.get_clients({ method = method })
|
||||||
else
|
else
|
||||||
---@diagnostic disable-next-line: deprecated
|
---@diagnostic disable-next-line: deprecated
|
||||||
|
|
@ -32,7 +32,7 @@ end
|
||||||
---@return boolean
|
---@return boolean
|
||||||
local function match_glob(glob, path)
|
local function match_glob(glob, path)
|
||||||
-- nvim-0.10 will have vim.glob.to_lpeg, so this will be a LPeg pattern
|
-- nvim-0.10 will have vim.glob.to_lpeg, so this will be a LPeg pattern
|
||||||
if type(glob) ~= 'string' then
|
if type(glob) ~= "string" then
|
||||||
return glob:match(path) ~= nil
|
return glob:match(path) ~= nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -59,7 +59,7 @@ local function get_matching_paths(client, filters, paths)
|
||||||
|
|
||||||
local match_fns = {}
|
local match_fns = {}
|
||||||
for _, filter in ipairs(filters) do
|
for _, filter in ipairs(filters) do
|
||||||
if filter.scheme == nil or filter.scheme == 'file' then
|
if filter.scheme == nil or filter.scheme == "file" then
|
||||||
local pattern = filter.pattern
|
local pattern = filter.pattern
|
||||||
local glob = pattern.glob
|
local glob = pattern.glob
|
||||||
local ignore_case = pattern.options and pattern.options.ignoreCase
|
local ignore_case = pattern.options and pattern.options.ignoreCase
|
||||||
|
|
@ -69,32 +69,32 @@ local function get_matching_paths(client, filters, paths)
|
||||||
|
|
||||||
-- Some language servers use forward slashes as path separators on Windows (LuaLS)
|
-- Some language servers use forward slashes as path separators on Windows (LuaLS)
|
||||||
-- We no longer need this after 0.12: https://github.com/neovim/neovim/commit/322a6d305d088420b23071c227af07b7c1beb41a
|
-- 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
|
if vim.fn.has("nvim-0.12") == 0 and fs.is_windows then
|
||||||
glob = glob:gsub('/', '\\')
|
glob = glob:gsub("/", "\\")
|
||||||
end
|
end
|
||||||
|
|
||||||
---@type string|vim.lpeg.Pattern
|
---@type string|vim.lpeg.Pattern
|
||||||
local glob_to_match = glob
|
local glob_to_match = glob
|
||||||
if vim.glob and vim.glob.to_lpeg then
|
if vim.glob and vim.glob.to_lpeg then
|
||||||
glob = glob:gsub('{(.-)}', function(s)
|
glob = glob:gsub("{(.-)}", function(s)
|
||||||
local patterns = vim.split(s, ',')
|
local patterns = vim.split(s, ",")
|
||||||
local filtered = {}
|
local filtered = {}
|
||||||
for _, pat in ipairs(patterns) do
|
for _, pat in ipairs(patterns) do
|
||||||
if pat ~= '' then
|
if pat ~= "" then
|
||||||
table.insert(filtered, pat)
|
table.insert(filtered, pat)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if #filtered == 0 then
|
if #filtered == 0 then
|
||||||
return ''
|
return ""
|
||||||
end
|
end
|
||||||
-- HACK around https://github.com/neovim/neovim/issues/28931
|
-- HACK around https://github.com/neovim/neovim/issues/28931
|
||||||
-- find alternations and sort them by length to try to match the longest first
|
-- find alternations and sort them by length to try to match the longest first
|
||||||
if vim.fn.has('nvim-0.11') == 0 then
|
if vim.fn.has("nvim-0.11") == 0 then
|
||||||
table.sort(filtered, function(a, b)
|
table.sort(filtered, function(a, b)
|
||||||
return a:len() > b:len()
|
return a:len() > b:len()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
return '{' .. table.concat(filtered, ',') .. '}'
|
return "{" .. table.concat(filtered, ",") .. "}"
|
||||||
end)
|
end)
|
||||||
|
|
||||||
glob_to_match = vim.glob.to_lpeg(glob)
|
glob_to_match = vim.glob.to_lpeg(glob)
|
||||||
|
|
@ -102,7 +102,7 @@ local function get_matching_paths(client, filters, paths)
|
||||||
local matches = pattern.matches
|
local matches = pattern.matches
|
||||||
table.insert(match_fns, function(path)
|
table.insert(match_fns, function(path)
|
||||||
local is_dir = vim.fn.isdirectory(path) == 1
|
local is_dir = vim.fn.isdirectory(path) == 1
|
||||||
if matches and ((matches == 'file' and is_dir) or (matches == 'folder' and not is_dir)) then
|
if matches and ((matches == "file" and is_dir) or (matches == "folder" and not is_dir)) then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -163,10 +163,10 @@ local function will_file_operation(method, capability_name, files, options)
|
||||||
for _, client in ipairs(clients) do
|
for _, client in ipairs(clients) do
|
||||||
local filters = vim.tbl_get(
|
local filters = vim.tbl_get(
|
||||||
client.server_capabilities,
|
client.server_capabilities,
|
||||||
'workspace',
|
"workspace",
|
||||||
'fileOperations',
|
"fileOperations",
|
||||||
capability_name,
|
capability_name,
|
||||||
'filters'
|
"filters"
|
||||||
)
|
)
|
||||||
local matching_files = get_matching_paths(client, filters, files)
|
local matching_files = get_matching_paths(client, filters, files)
|
||||||
if matching_files then
|
if matching_files then
|
||||||
|
|
@ -178,7 +178,7 @@ local function will_file_operation(method, capability_name, files, options)
|
||||||
end, matching_files),
|
end, matching_files),
|
||||||
}
|
}
|
||||||
local result, err
|
local result, err
|
||||||
if vim.fn.has('nvim-0.11') == 1 then
|
if vim.fn.has("nvim-0.11") == 1 then
|
||||||
result, err = client:request_sync(method, params, options.timeout_ms or 1000, 0)
|
result, err = client:request_sync(method, params, options.timeout_ms or 1000, 0)
|
||||||
else
|
else
|
||||||
---@diagnostic disable-next-line: param-type-mismatch
|
---@diagnostic disable-next-line: param-type-mismatch
|
||||||
|
|
@ -205,10 +205,10 @@ local function did_file_operation(method, capability_name, files)
|
||||||
for _, client in ipairs(clients) do
|
for _, client in ipairs(clients) do
|
||||||
local filters = vim.tbl_get(
|
local filters = vim.tbl_get(
|
||||||
client.server_capabilities,
|
client.server_capabilities,
|
||||||
'workspace',
|
"workspace",
|
||||||
'fileOperations',
|
"fileOperations",
|
||||||
capability_name,
|
capability_name,
|
||||||
'filters'
|
"filters"
|
||||||
)
|
)
|
||||||
local matching_files = get_matching_paths(client, filters, files)
|
local matching_files = get_matching_paths(client, filters, files)
|
||||||
if matching_files then
|
if matching_files then
|
||||||
|
|
@ -219,7 +219,7 @@ local function did_file_operation(method, capability_name, files)
|
||||||
}
|
}
|
||||||
end, matching_files),
|
end, matching_files),
|
||||||
}
|
}
|
||||||
if vim.fn.has('nvim-0.11') == 1 then
|
if vim.fn.has("nvim-0.11") == 1 then
|
||||||
client:notify(method, params)
|
client:notify(method, params)
|
||||||
else
|
else
|
||||||
---@diagnostic disable-next-line: param-type-mismatch
|
---@diagnostic disable-next-line: param-type-mismatch
|
||||||
|
|
@ -239,13 +239,13 @@ end
|
||||||
---@return nil|{edit: lsp.WorkspaceEdit, offset_encoding: string}[]
|
---@return nil|{edit: lsp.WorkspaceEdit, offset_encoding: string}[]
|
||||||
---@return nil|string|lsp.ResponseError err
|
---@return nil|string|lsp.ResponseError err
|
||||||
function M.will_create_files(files, options)
|
function M.will_create_files(files, options)
|
||||||
return will_file_operation(ms.workspace_willCreateFiles, 'willCreate', files, options)
|
return will_file_operation(ms.workspace_willCreateFiles, "willCreate", files, options)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Notify the server that files were created from within the client.
|
--- Notify the server that files were created from within the client.
|
||||||
---@param files string[] The files and folders that will be created
|
---@param files string[] The files and folders that will be created
|
||||||
function M.did_create_files(files)
|
function M.did_create_files(files)
|
||||||
did_file_operation(ms.workspace_didCreateFiles, 'didCreate', files)
|
did_file_operation(ms.workspace_didCreateFiles, "didCreate", files)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Notify the server that the client is about to delete files.
|
--- Notify the server that the client is about to delete files.
|
||||||
|
|
@ -258,13 +258,13 @@ end
|
||||||
---@return nil|{edit: lsp.WorkspaceEdit, offset_encoding: string}[]
|
---@return nil|{edit: lsp.WorkspaceEdit, offset_encoding: string}[]
|
||||||
---@return nil|string|lsp.ResponseError err
|
---@return nil|string|lsp.ResponseError err
|
||||||
function M.will_delete_files(files, options)
|
function M.will_delete_files(files, options)
|
||||||
return will_file_operation(ms.workspace_willDeleteFiles, 'willDelete', files, options)
|
return will_file_operation(ms.workspace_willDeleteFiles, "willDelete", files, options)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Notify the server that files were deleted from within the client.
|
--- Notify the server that files were deleted from within the client.
|
||||||
---@param files string[] The files and folders that were deleted
|
---@param files string[] The files and folders that were deleted
|
||||||
function M.did_delete_files(files)
|
function M.did_delete_files(files)
|
||||||
did_file_operation(ms.workspace_didDeleteFiles, 'didDelete', files)
|
did_file_operation(ms.workspace_didDeleteFiles, "didDelete", files)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Notify the server that the client is about to rename files.
|
--- Notify the server that the client is about to rename files.
|
||||||
|
|
@ -284,10 +284,10 @@ function M.will_rename_files(files, options)
|
||||||
for _, client in ipairs(clients) do
|
for _, client in ipairs(clients) do
|
||||||
local filters = vim.tbl_get(
|
local filters = vim.tbl_get(
|
||||||
client.server_capabilities,
|
client.server_capabilities,
|
||||||
'workspace',
|
"workspace",
|
||||||
'fileOperations',
|
"fileOperations",
|
||||||
'willRename',
|
"willRename",
|
||||||
'filters'
|
"filters"
|
||||||
)
|
)
|
||||||
local matching_files = get_matching_paths(client, filters, vim.tbl_keys(files))
|
local matching_files = get_matching_paths(client, filters, vim.tbl_keys(files))
|
||||||
if matching_files then
|
if matching_files then
|
||||||
|
|
@ -300,7 +300,7 @@ function M.will_rename_files(files, options)
|
||||||
end, matching_files),
|
end, matching_files),
|
||||||
}
|
}
|
||||||
local result, err
|
local result, err
|
||||||
if vim.fn.has('nvim-0.11') == 1 then
|
if vim.fn.has("nvim-0.11") == 1 then
|
||||||
result, err =
|
result, err =
|
||||||
client:request_sync(ms.workspace_willRenameFiles, params, options.timeout_ms or 1000, 0)
|
client:request_sync(ms.workspace_willRenameFiles, params, options.timeout_ms or 1000, 0)
|
||||||
else
|
else
|
||||||
|
|
@ -327,7 +327,7 @@ function M.did_rename_files(files)
|
||||||
local clients = get_clients(ms.workspace_didRenameFiles)
|
local clients = get_clients(ms.workspace_didRenameFiles)
|
||||||
for _, client in ipairs(clients) do
|
for _, client in ipairs(clients) do
|
||||||
local filters =
|
local filters =
|
||||||
vim.tbl_get(client.server_capabilities, 'workspace', 'fileOperations', 'didRename', 'filters')
|
vim.tbl_get(client.server_capabilities, "workspace", "fileOperations", "didRename", "filters")
|
||||||
local matching_files = get_matching_paths(client, filters, vim.tbl_keys(files))
|
local matching_files = get_matching_paths(client, filters, vim.tbl_keys(files))
|
||||||
if matching_files then
|
if matching_files then
|
||||||
local params = {
|
local params = {
|
||||||
|
|
@ -338,7 +338,7 @@ function M.did_rename_files(files)
|
||||||
}
|
}
|
||||||
end, matching_files),
|
end, matching_files),
|
||||||
}
|
}
|
||||||
if vim.fn.has('nvim-0.11') == 1 then
|
if vim.fn.has("nvim-0.11") == 1 then
|
||||||
client:notify(ms.workspace_didRenameFiles, params)
|
client:notify(ms.workspace_didRenameFiles, params)
|
||||||
else
|
else
|
||||||
---@diagnostic disable-next-line: param-type-mismatch
|
---@diagnostic disable-next-line: param-type-mismatch
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
local columns = require('canola.columns')
|
local columns = require("oil.columns")
|
||||||
local config = require('canola.config')
|
local config = require("oil.config")
|
||||||
local layout = require('canola.layout')
|
local layout = require("oil.layout")
|
||||||
local util = require('canola.util')
|
local util = require("oil.util")
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
---@param actions canola.Action[]
|
---@param actions oil.Action[]
|
||||||
---@return boolean
|
---@return boolean
|
||||||
local function is_simple_edit(actions)
|
local function is_simple_edit(actions)
|
||||||
local num_create = 0
|
local num_create = 0
|
||||||
|
|
@ -12,17 +12,17 @@ local function is_simple_edit(actions)
|
||||||
local num_move = 0
|
local num_move = 0
|
||||||
for _, action in ipairs(actions) do
|
for _, action in ipairs(actions) do
|
||||||
-- If there are any deletes, it is not a simple edit
|
-- If there are any deletes, it is not a simple edit
|
||||||
if action.type == 'delete' then
|
if action.type == "delete" then
|
||||||
return false
|
return false
|
||||||
elseif action.type == 'create' then
|
elseif action.type == "create" then
|
||||||
num_create = num_create + 1
|
num_create = num_create + 1
|
||||||
elseif action.type == 'copy' then
|
elseif action.type == "copy" then
|
||||||
num_copy = num_copy + 1
|
num_copy = num_copy + 1
|
||||||
-- Cross-adapter copies are not simple
|
-- Cross-adapter copies are not simple
|
||||||
if util.parse_url(action.src_url) ~= util.parse_url(action.dest_url) then
|
if util.parse_url(action.src_url) ~= util.parse_url(action.dest_url) then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
elseif action.type == 'move' then
|
elseif action.type == "move" then
|
||||||
num_move = num_move + 1
|
num_move = num_move + 1
|
||||||
-- Cross-adapter moves are not simple
|
-- Cross-adapter moves are not simple
|
||||||
if util.parse_url(action.src_url) ~= util.parse_url(action.dest_url) then
|
if util.parse_url(action.src_url) ~= util.parse_url(action.dest_url) then
|
||||||
|
|
@ -46,14 +46,14 @@ end
|
||||||
---@param lines string[]
|
---@param lines string[]
|
||||||
local function render_lines(winid, bufnr, lines)
|
local function render_lines(winid, bufnr, lines)
|
||||||
util.render_text(bufnr, lines, {
|
util.render_text(bufnr, lines, {
|
||||||
v_align = 'top',
|
v_align = "top",
|
||||||
h_align = 'left',
|
h_align = "left",
|
||||||
winid = winid,
|
winid = winid,
|
||||||
actions = { '[Y]es', '[N]o' },
|
actions = { "[Y]es", "[N]o" },
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param actions canola.Action[]
|
---@param actions oil.Action[]
|
||||||
---@param should_confirm nil|boolean
|
---@param should_confirm nil|boolean
|
||||||
---@param cb fun(proceed: boolean)
|
---@param cb fun(proceed: boolean)
|
||||||
M.show = vim.schedule_wrap(function(actions, should_confirm, cb)
|
M.show = vim.schedule_wrap(function(actions, should_confirm, cb)
|
||||||
|
|
@ -67,67 +67,51 @@ M.show = vim.schedule_wrap(function(actions, should_confirm, cb)
|
||||||
cb(true)
|
cb(true)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
if should_confirm == nil and config.skip_confirm_for_delete then
|
|
||||||
local all_deletes = true
|
|
||||||
for _, action in ipairs(actions) do
|
|
||||||
if action.type ~= 'delete' then
|
|
||||||
all_deletes = false
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if all_deletes then
|
|
||||||
cb(true)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Create the buffer
|
-- Create the buffer
|
||||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||||
vim.bo[bufnr].bufhidden = 'wipe'
|
vim.bo[bufnr].bufhidden = "wipe"
|
||||||
local lines = {}
|
local lines = {}
|
||||||
local max_line_width = 0
|
local max_line_width = 0
|
||||||
for _, action in ipairs(actions) do
|
for _, action in ipairs(actions) do
|
||||||
local adapter = util.get_adapter_for_action(action)
|
local adapter = util.get_adapter_for_action(action)
|
||||||
local line
|
local line
|
||||||
if action.type == 'change' then
|
if action.type == "change" then
|
||||||
---@cast action canola.ChangeAction
|
---@cast action oil.ChangeAction
|
||||||
line = columns.render_change_action(adapter, action)
|
line = columns.render_change_action(adapter, action)
|
||||||
else
|
else
|
||||||
line = adapter.render_action(action)
|
line = adapter.render_action(action)
|
||||||
end
|
end
|
||||||
-- We can't handle lines with newlines in them
|
-- We can't handle lines with newlines in them
|
||||||
line = line:gsub('\n', '')
|
line = line:gsub("\n", "")
|
||||||
table.insert(lines, line)
|
table.insert(lines, line)
|
||||||
local line_width = vim.api.nvim_strwidth(line)
|
local line_width = vim.api.nvim_strwidth(line)
|
||||||
if line_width > max_line_width then
|
if line_width > max_line_width then
|
||||||
max_line_width = line_width
|
max_line_width = line_width
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
table.insert(lines, '')
|
table.insert(lines, "")
|
||||||
|
|
||||||
-- Create the floating window
|
-- Create the floating window
|
||||||
local width, height = layout.calculate_dims(max_line_width, #lines + 1, config.confirmation)
|
local width, height = layout.calculate_dims(max_line_width, #lines + 1, config.confirmation)
|
||||||
local ok, winid = pcall(vim.api.nvim_open_win, bufnr, true, {
|
local ok, winid = pcall(vim.api.nvim_open_win, bufnr, true, {
|
||||||
relative = 'editor',
|
relative = "editor",
|
||||||
width = width,
|
width = width,
|
||||||
height = height,
|
height = height,
|
||||||
row = math.floor((layout.get_editor_height() - height) / 2),
|
row = math.floor((layout.get_editor_height() - height) / 2),
|
||||||
col = math.floor((layout.get_editor_width() - width) / 2),
|
col = math.floor((layout.get_editor_width() - width) / 2),
|
||||||
zindex = 152, -- render on top of the floating window title
|
zindex = 152, -- render on top of the floating window title
|
||||||
style = 'minimal',
|
style = "minimal",
|
||||||
border = config.confirmation.border,
|
border = config.confirmation.border,
|
||||||
})
|
})
|
||||||
if not ok then
|
if not ok then
|
||||||
vim.notify(
|
vim.notify(string.format("Error showing oil preview window: %s", winid), vim.log.levels.ERROR)
|
||||||
string.format('Error showing canola preview window: %s', winid),
|
|
||||||
vim.log.levels.ERROR
|
|
||||||
)
|
|
||||||
cb(false)
|
cb(false)
|
||||||
end
|
end
|
||||||
vim.bo[bufnr].filetype = 'canola_preview'
|
vim.bo[bufnr].filetype = "oil_preview"
|
||||||
vim.bo[bufnr].syntax = 'canola_preview'
|
vim.bo[bufnr].syntax = "oil_preview"
|
||||||
for k, v in pairs(config.confirmation.win_options) do
|
for k, v in pairs(config.confirmation.win_options) do
|
||||||
vim.api.nvim_set_option_value(k, v, { scope = 'local', win = winid })
|
vim.api.nvim_set_option_value(k, v, { scope = "local", win = winid })
|
||||||
end
|
end
|
||||||
|
|
||||||
render_lines(winid, bufnr, lines)
|
render_lines(winid, bufnr, lines)
|
||||||
|
|
@ -153,7 +137,7 @@ M.show = vim.schedule_wrap(function(actions, should_confirm, cb)
|
||||||
end
|
end
|
||||||
cancel = make_callback(false)
|
cancel = make_callback(false)
|
||||||
confirm = make_callback(true)
|
confirm = make_callback(true)
|
||||||
vim.api.nvim_create_autocmd('BufLeave', {
|
vim.api.nvim_create_autocmd("BufLeave", {
|
||||||
callback = function()
|
callback = function()
|
||||||
cancel()
|
cancel()
|
||||||
end,
|
end,
|
||||||
|
|
@ -161,7 +145,7 @@ M.show = vim.schedule_wrap(function(actions, should_confirm, cb)
|
||||||
nested = true,
|
nested = true,
|
||||||
buffer = bufnr,
|
buffer = bufnr,
|
||||||
})
|
})
|
||||||
vim.api.nvim_create_autocmd('WinLeave', {
|
vim.api.nvim_create_autocmd("WinLeave", {
|
||||||
callback = function()
|
callback = function()
|
||||||
cancel()
|
cancel()
|
||||||
end,
|
end,
|
||||||
|
|
@ -170,12 +154,12 @@ M.show = vim.schedule_wrap(function(actions, should_confirm, cb)
|
||||||
})
|
})
|
||||||
table.insert(
|
table.insert(
|
||||||
autocmds,
|
autocmds,
|
||||||
vim.api.nvim_create_autocmd('VimResized', {
|
vim.api.nvim_create_autocmd("VimResized", {
|
||||||
callback = function()
|
callback = function()
|
||||||
if vim.api.nvim_win_is_valid(winid) then
|
if vim.api.nvim_win_is_valid(winid) then
|
||||||
width, height = layout.calculate_dims(max_line_width, #lines, config.confirmation)
|
width, height = layout.calculate_dims(max_line_width, #lines, config.confirmation)
|
||||||
vim.api.nvim_win_set_config(winid, {
|
vim.api.nvim_win_set_config(winid, {
|
||||||
relative = 'editor',
|
relative = "editor",
|
||||||
width = width,
|
width = width,
|
||||||
height = height,
|
height = height,
|
||||||
row = math.floor((layout.get_editor_height() - height) / 2),
|
row = math.floor((layout.get_editor_height() - height) / 2),
|
||||||
|
|
@ -189,17 +173,17 @@ M.show = vim.schedule_wrap(function(actions, should_confirm, cb)
|
||||||
)
|
)
|
||||||
|
|
||||||
-- We used to use [C]ancel to cancel, so preserve the old keymap
|
-- We used to use [C]ancel to cancel, so preserve the old keymap
|
||||||
local cancel_keys = { 'n', 'N', 'c', 'C', 'q', '<C-c>', '<Esc>' }
|
local cancel_keys = { "n", "N", "c", "C", "q", "<C-c>", "<Esc>" }
|
||||||
for _, cancel_key in ipairs(cancel_keys) do
|
for _, cancel_key in ipairs(cancel_keys) do
|
||||||
vim.keymap.set('n', cancel_key, function()
|
vim.keymap.set("n", cancel_key, function()
|
||||||
cancel()
|
cancel()
|
||||||
end, { buffer = bufnr, nowait = true })
|
end, { buffer = bufnr, nowait = true })
|
||||||
end
|
end
|
||||||
|
|
||||||
-- We used to use [O]k to confirm, so preserve the old keymap
|
-- We used to use [O]k to confirm, so preserve the old keymap
|
||||||
local confirm_keys = { 'y', 'Y', 'o', 'O' }
|
local confirm_keys = { "y", "Y", "o", "O" }
|
||||||
for _, confirm_key in ipairs(confirm_keys) do
|
for _, confirm_key in ipairs(confirm_keys) do
|
||||||
vim.keymap.set('n', confirm_key, function()
|
vim.keymap.set("n", confirm_key, function()
|
||||||
confirm()
|
confirm()
|
||||||
end, { buffer = bufnr, nowait = true })
|
end, { buffer = bufnr, nowait = true })
|
||||||
end
|
end
|
||||||
|
|
@ -1,60 +1,60 @@
|
||||||
local Progress = require('canola.mutator.progress')
|
local Progress = require("oil.mutator.progress")
|
||||||
local Trie = require('canola.mutator.trie')
|
local Trie = require("oil.mutator.trie")
|
||||||
local cache = require('canola.cache')
|
local cache = require("oil.cache")
|
||||||
local canola = require('canola')
|
local columns = require("oil.columns")
|
||||||
local columns = require('canola.columns')
|
local config = require("oil.config")
|
||||||
local config = require('canola.config')
|
local confirmation = require("oil.mutator.confirmation")
|
||||||
local confirmation = require('canola.mutator.confirmation')
|
local constants = require("oil.constants")
|
||||||
local constants = require('canola.constants')
|
local fs = require("oil.fs")
|
||||||
local fs = require('canola.fs')
|
local lsp_helpers = require("oil.lsp.helpers")
|
||||||
local lsp_helpers = require('canola.lsp.helpers')
|
local oil = require("oil")
|
||||||
local parser = require('canola.mutator.parser')
|
local parser = require("oil.mutator.parser")
|
||||||
local util = require('canola.util')
|
local util = require("oil.util")
|
||||||
local view = require('canola.view')
|
local view = require("oil.view")
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local FIELD_NAME = constants.FIELD_NAME
|
local FIELD_NAME = constants.FIELD_NAME
|
||||||
local FIELD_TYPE = constants.FIELD_TYPE
|
local FIELD_TYPE = constants.FIELD_TYPE
|
||||||
|
|
||||||
---@alias canola.Action canola.CreateAction|canola.DeleteAction|canola.MoveAction|canola.CopyAction|canola.ChangeAction
|
---@alias oil.Action oil.CreateAction|oil.DeleteAction|oil.MoveAction|oil.CopyAction|oil.ChangeAction
|
||||||
|
|
||||||
---@class (exact) canola.CreateAction
|
---@class (exact) oil.CreateAction
|
||||||
---@field type "create"
|
---@field type "create"
|
||||||
---@field url string
|
---@field url string
|
||||||
---@field entry_type canola.EntryType
|
---@field entry_type oil.EntryType
|
||||||
---@field link nil|string
|
---@field link nil|string
|
||||||
|
|
||||||
---@class (exact) canola.DeleteAction
|
---@class (exact) oil.DeleteAction
|
||||||
---@field type "delete"
|
---@field type "delete"
|
||||||
---@field url string
|
---@field url string
|
||||||
---@field entry_type canola.EntryType
|
---@field entry_type oil.EntryType
|
||||||
|
|
||||||
---@class (exact) canola.MoveAction
|
---@class (exact) oil.MoveAction
|
||||||
---@field type "move"
|
---@field type "move"
|
||||||
---@field entry_type canola.EntryType
|
---@field entry_type oil.EntryType
|
||||||
---@field src_url string
|
---@field src_url string
|
||||||
---@field dest_url string
|
---@field dest_url string
|
||||||
|
|
||||||
---@class (exact) canola.CopyAction
|
---@class (exact) oil.CopyAction
|
||||||
---@field type "copy"
|
---@field type "copy"
|
||||||
---@field entry_type canola.EntryType
|
---@field entry_type oil.EntryType
|
||||||
---@field src_url string
|
---@field src_url string
|
||||||
---@field dest_url string
|
---@field dest_url string
|
||||||
|
|
||||||
---@class (exact) canola.ChangeAction
|
---@class (exact) oil.ChangeAction
|
||||||
---@field type "change"
|
---@field type "change"
|
||||||
---@field entry_type canola.EntryType
|
---@field entry_type oil.EntryType
|
||||||
---@field url string
|
---@field url string
|
||||||
---@field column string
|
---@field column string
|
||||||
---@field value any
|
---@field value any
|
||||||
|
|
||||||
---@param all_diffs table<integer, canola.Diff[]>
|
---@param all_diffs table<integer, oil.Diff[]>
|
||||||
---@return canola.Action[]
|
---@return oil.Action[]
|
||||||
M.create_actions_from_diffs = function(all_diffs)
|
M.create_actions_from_diffs = function(all_diffs)
|
||||||
---@type canola.Action[]
|
---@type oil.Action[]
|
||||||
local actions = {}
|
local actions = {}
|
||||||
|
|
||||||
---@type table<integer, canola.Diff[]>
|
---@type table<integer, oil.Diff[]>
|
||||||
local diff_by_id = setmetatable({}, {
|
local diff_by_id = setmetatable({}, {
|
||||||
__index = function(t, key)
|
__index = function(t, key)
|
||||||
local list = {}
|
local list = {}
|
||||||
|
|
@ -69,11 +69,11 @@ M.create_actions_from_diffs = function(all_diffs)
|
||||||
-- > foo/bar/b.txt
|
-- > foo/bar/b.txt
|
||||||
local seen_creates = {}
|
local seen_creates = {}
|
||||||
|
|
||||||
---@param action canola.Action
|
---@param action oil.Action
|
||||||
local function add_action(action)
|
local function add_action(action)
|
||||||
local adapter = assert(config.get_adapter_by_scheme(action.dest_url or action.url))
|
local adapter = assert(config.get_adapter_by_scheme(action.dest_url or action.url))
|
||||||
if not adapter.filter_action or adapter.filter_action(action) then
|
if not adapter.filter_action or adapter.filter_action(action) then
|
||||||
if action.type == 'create' then
|
if action.type == "create" then
|
||||||
if seen_creates[action.url] then
|
if seen_creates[action.url] then
|
||||||
return
|
return
|
||||||
else
|
else
|
||||||
|
|
@ -87,11 +87,11 @@ M.create_actions_from_diffs = function(all_diffs)
|
||||||
for bufnr, diffs in pairs(all_diffs) do
|
for bufnr, diffs in pairs(all_diffs) do
|
||||||
local adapter = util.get_adapter(bufnr, true)
|
local adapter = util.get_adapter(bufnr, true)
|
||||||
if not adapter then
|
if not adapter then
|
||||||
error('Missing adapter')
|
error("Missing adapter")
|
||||||
end
|
end
|
||||||
local parent_url = vim.api.nvim_buf_get_name(bufnr)
|
local parent_url = vim.api.nvim_buf_get_name(bufnr)
|
||||||
for _, diff in ipairs(diffs) do
|
for _, diff in ipairs(diffs) do
|
||||||
if diff.type == 'new' then
|
if diff.type == "new" then
|
||||||
if diff.id then
|
if diff.id then
|
||||||
local by_id = diff_by_id[diff.id]
|
local by_id = diff_by_id[diff.id]
|
||||||
---HACK: set the destination on this diff for use later
|
---HACK: set the destination on this diff for use later
|
||||||
|
|
@ -100,28 +100,28 @@ M.create_actions_from_diffs = function(all_diffs)
|
||||||
table.insert(by_id, diff)
|
table.insert(by_id, diff)
|
||||||
else
|
else
|
||||||
-- Parse nested files like foo/bar/baz
|
-- Parse nested files like foo/bar/baz
|
||||||
local path_sep = fs.is_windows and '[/\\]' or '/'
|
local path_sep = fs.is_windows and "[/\\]" or "/"
|
||||||
local pieces = vim.split(diff.name, path_sep)
|
local pieces = vim.split(diff.name, path_sep)
|
||||||
local url = parent_url:gsub('/$', '')
|
local url = parent_url:gsub("/$", "")
|
||||||
for i, v in ipairs(pieces) do
|
for i, v in ipairs(pieces) do
|
||||||
local is_last = i == #pieces
|
local is_last = i == #pieces
|
||||||
local entry_type = is_last and diff.entry_type or 'directory'
|
local entry_type = is_last and diff.entry_type or "directory"
|
||||||
local alternation = v:match('{([^}]+)}')
|
local alternation = v:match("{([^}]+)}")
|
||||||
if is_last and alternation then
|
if is_last and alternation then
|
||||||
-- Parse alternations like foo.{js,test.js}
|
-- Parse alternations like foo.{js,test.js}
|
||||||
for _, alt in ipairs(vim.split(alternation, ',')) do
|
for _, alt in ipairs(vim.split(alternation, ",")) do
|
||||||
local alt_url = url .. '/' .. v:gsub('{[^}]+}', alt)
|
local alt_url = url .. "/" .. v:gsub("{[^}]+}", alt)
|
||||||
add_action({
|
add_action({
|
||||||
type = 'create',
|
type = "create",
|
||||||
url = alt_url,
|
url = alt_url,
|
||||||
entry_type = entry_type,
|
entry_type = entry_type,
|
||||||
link = diff.link,
|
link = diff.link,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
url = url .. '/' .. v
|
url = url .. "/" .. v
|
||||||
add_action({
|
add_action({
|
||||||
type = 'create',
|
type = "create",
|
||||||
url = url,
|
url = url,
|
||||||
entry_type = entry_type,
|
entry_type = entry_type,
|
||||||
link = diff.link,
|
link = diff.link,
|
||||||
|
|
@ -129,9 +129,9 @@ M.create_actions_from_diffs = function(all_diffs)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
elseif diff.type == 'change' then
|
elseif diff.type == "change" then
|
||||||
add_action({
|
add_action({
|
||||||
type = 'change',
|
type = "change",
|
||||||
url = parent_url .. diff.name,
|
url = parent_url .. diff.name,
|
||||||
entry_type = diff.entry_type,
|
entry_type = diff.entry_type,
|
||||||
column = diff.column,
|
column = diff.column,
|
||||||
|
|
@ -151,7 +151,7 @@ M.create_actions_from_diffs = function(all_diffs)
|
||||||
for id, diffs in pairs(diff_by_id) do
|
for id, diffs in pairs(diff_by_id) do
|
||||||
local entry = cache.get_entry_by_id(id)
|
local entry = cache.get_entry_by_id(id)
|
||||||
if not entry then
|
if not entry then
|
||||||
error(string.format('Could not find entry %d', id))
|
error(string.format("Could not find entry %d", id))
|
||||||
end
|
end
|
||||||
---HACK: access the has_delete field on the list-like table of diffs
|
---HACK: access the has_delete field on the list-like table of diffs
|
||||||
---@diagnostic disable-next-line: undefined-field
|
---@diagnostic disable-next-line: undefined-field
|
||||||
|
|
@ -161,7 +161,7 @@ M.create_actions_from_diffs = function(all_diffs)
|
||||||
-- MOVE (+ optional copies) when has both creates and delete
|
-- MOVE (+ optional copies) when has both creates and delete
|
||||||
for i, diff in ipairs(diffs) do
|
for i, diff in ipairs(diffs) do
|
||||||
add_action({
|
add_action({
|
||||||
type = i == #diffs and 'move' or 'copy',
|
type = i == #diffs and "move" or "copy",
|
||||||
entry_type = entry[FIELD_TYPE],
|
entry_type = entry[FIELD_TYPE],
|
||||||
---HACK: access the dest field we set above
|
---HACK: access the dest field we set above
|
||||||
---@diagnostic disable-next-line: undefined-field
|
---@diagnostic disable-next-line: undefined-field
|
||||||
|
|
@ -172,7 +172,7 @@ M.create_actions_from_diffs = function(all_diffs)
|
||||||
else
|
else
|
||||||
-- DELETE when no create
|
-- DELETE when no create
|
||||||
add_action({
|
add_action({
|
||||||
type = 'delete',
|
type = "delete",
|
||||||
entry_type = entry[FIELD_TYPE],
|
entry_type = entry[FIELD_TYPE],
|
||||||
url = cache.get_parent_url(id) .. entry[FIELD_NAME],
|
url = cache.get_parent_url(id) .. entry[FIELD_NAME],
|
||||||
})
|
})
|
||||||
|
|
@ -181,7 +181,7 @@ M.create_actions_from_diffs = function(all_diffs)
|
||||||
-- COPY when create but no delete
|
-- COPY when create but no delete
|
||||||
for _, diff in ipairs(diffs) do
|
for _, diff in ipairs(diffs) do
|
||||||
add_action({
|
add_action({
|
||||||
type = 'copy',
|
type = "copy",
|
||||||
entry_type = entry[FIELD_TYPE],
|
entry_type = entry[FIELD_TYPE],
|
||||||
src_url = cache.get_parent_url(id) .. entry[FIELD_NAME],
|
src_url = cache.get_parent_url(id) .. entry[FIELD_NAME],
|
||||||
---HACK: access the dest field we set above
|
---HACK: access the dest field we set above
|
||||||
|
|
@ -195,15 +195,15 @@ M.create_actions_from_diffs = function(all_diffs)
|
||||||
return M.enforce_action_order(actions)
|
return M.enforce_action_order(actions)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param actions canola.Action[]
|
---@param actions oil.Action[]
|
||||||
---@return canola.Action[]
|
---@return oil.Action[]
|
||||||
M.enforce_action_order = function(actions)
|
M.enforce_action_order = function(actions)
|
||||||
local src_trie = Trie.new()
|
local src_trie = Trie.new()
|
||||||
local dest_trie = Trie.new()
|
local dest_trie = Trie.new()
|
||||||
for _, action in ipairs(actions) do
|
for _, action in ipairs(actions) do
|
||||||
if action.type == 'delete' or action.type == 'change' then
|
if action.type == "delete" or action.type == "change" then
|
||||||
src_trie:insert_action(action.url, action)
|
src_trie:insert_action(action.url, action)
|
||||||
elseif action.type == 'create' then
|
elseif action.type == "create" then
|
||||||
dest_trie:insert_action(action.url, action)
|
dest_trie:insert_action(action.url, action)
|
||||||
else
|
else
|
||||||
dest_trie:insert_action(action.dest_url, action)
|
dest_trie:insert_action(action.dest_url, action)
|
||||||
|
|
@ -220,21 +220,21 @@ M.enforce_action_order = function(actions)
|
||||||
|
|
||||||
---Gets the dependencies of a particular action. Effectively dynamically calculates the dependency
|
---Gets the dependencies of a particular action. Effectively dynamically calculates the dependency
|
||||||
---"edges" of the graph.
|
---"edges" of the graph.
|
||||||
---@param action canola.Action
|
---@param action oil.Action
|
||||||
local function get_deps(action)
|
local function get_deps(action)
|
||||||
local ret = {}
|
local ret = {}
|
||||||
if action.type == 'delete' then
|
if action.type == "delete" then
|
||||||
src_trie:accum_children_of(action.url, ret)
|
src_trie:accum_children_of(action.url, ret)
|
||||||
elseif action.type == 'create' then
|
elseif action.type == "create" then
|
||||||
-- Finish operating on parents first
|
-- Finish operating on parents first
|
||||||
-- e.g. NEW /a BEFORE NEW /a/b
|
-- e.g. NEW /a BEFORE NEW /a/b
|
||||||
dest_trie:accum_first_parents_of(action.url, ret)
|
dest_trie:accum_first_parents_of(action.url, ret)
|
||||||
-- Process remove path before creating new path
|
-- Process remove path before creating new path
|
||||||
-- e.g. DELETE /a BEFORE NEW /a
|
-- e.g. DELETE /a BEFORE NEW /a
|
||||||
src_trie:accum_actions_at(action.url, ret, function(a)
|
src_trie:accum_actions_at(action.url, ret, function(a)
|
||||||
return a.type == 'move' or a.type == 'delete'
|
return a.type == "move" or a.type == "delete"
|
||||||
end)
|
end)
|
||||||
elseif action.type == 'change' then
|
elseif action.type == "change" then
|
||||||
-- Finish operating on parents first
|
-- Finish operating on parents first
|
||||||
-- e.g. NEW /a BEFORE CHANGE /a/b
|
-- e.g. NEW /a BEFORE CHANGE /a/b
|
||||||
dest_trie:accum_first_parents_of(action.url, ret)
|
dest_trie:accum_first_parents_of(action.url, ret)
|
||||||
|
|
@ -244,9 +244,9 @@ M.enforce_action_order = function(actions)
|
||||||
-- Finish copy from operations first
|
-- Finish copy from operations first
|
||||||
-- e.g. COPY /a -> /b BEFORE CHANGE /a
|
-- e.g. COPY /a -> /b BEFORE CHANGE /a
|
||||||
src_trie:accum_actions_at(action.url, ret, function(entry)
|
src_trie:accum_actions_at(action.url, ret, function(entry)
|
||||||
return entry.type == 'copy'
|
return entry.type == "copy"
|
||||||
end)
|
end)
|
||||||
elseif action.type == 'move' then
|
elseif action.type == "move" then
|
||||||
-- Finish operating on parents first
|
-- Finish operating on parents first
|
||||||
-- e.g. NEW /a BEFORE MOVE /z -> /a/b
|
-- e.g. NEW /a BEFORE MOVE /z -> /a/b
|
||||||
dest_trie:accum_first_parents_of(action.dest_url, ret)
|
dest_trie:accum_first_parents_of(action.dest_url, ret)
|
||||||
|
|
@ -260,9 +260,9 @@ M.enforce_action_order = function(actions)
|
||||||
-- Process remove path before moving to new path
|
-- Process remove path before moving to new path
|
||||||
-- e.g. MOVE /a -> /b BEFORE MOVE /c -> /a
|
-- e.g. MOVE /a -> /b BEFORE MOVE /c -> /a
|
||||||
src_trie:accum_actions_at(action.dest_url, ret, function(a)
|
src_trie:accum_actions_at(action.dest_url, ret, function(a)
|
||||||
return a.type == 'move' or a.type == 'delete'
|
return a.type == "move" or a.type == "delete"
|
||||||
end)
|
end)
|
||||||
elseif action.type == 'copy' then
|
elseif action.type == "copy" then
|
||||||
-- Finish operating on parents first
|
-- Finish operating on parents first
|
||||||
-- e.g. NEW /a BEFORE COPY /z -> /a/b
|
-- e.g. NEW /a BEFORE COPY /z -> /a/b
|
||||||
dest_trie:accum_first_parents_of(action.dest_url, ret)
|
dest_trie:accum_first_parents_of(action.dest_url, ret)
|
||||||
|
|
@ -272,14 +272,14 @@ M.enforce_action_order = function(actions)
|
||||||
-- Process remove path before copying to new path
|
-- Process remove path before copying to new path
|
||||||
-- e.g. MOVE /a -> /b BEFORE COPY /c -> /a
|
-- e.g. MOVE /a -> /b BEFORE COPY /c -> /a
|
||||||
src_trie:accum_actions_at(action.dest_url, ret, function(a)
|
src_trie:accum_actions_at(action.dest_url, ret, function(a)
|
||||||
return a.type == 'move' or a.type == 'delete'
|
return a.type == "move" or a.type == "delete"
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
---@return nil|canola.Action The leaf action
|
---@return nil|oil.Action The leaf action
|
||||||
---@return nil|canola.Action When no leaves found, this is the last action in the loop
|
---@return nil|oil.Action When no leaves found, this is the last action in the loop
|
||||||
local function find_leaf(action, seen)
|
local function find_leaf(action, seen)
|
||||||
if not seen then
|
if not seen then
|
||||||
seen = {}
|
seen = {}
|
||||||
|
|
@ -312,24 +312,24 @@ M.enforce_action_order = function(actions)
|
||||||
if selected then
|
if selected then
|
||||||
to_remove = selected
|
to_remove = selected
|
||||||
else
|
else
|
||||||
if loop_action and loop_action.type == 'move' then
|
if loop_action and loop_action.type == "move" then
|
||||||
-- If this is moving a parent into itself, that's an error
|
-- If this is moving a parent into itself, that's an error
|
||||||
if vim.startswith(loop_action.dest_url, loop_action.src_url) then
|
if vim.startswith(loop_action.dest_url, loop_action.src_url) then
|
||||||
error('Detected cycle in desired paths')
|
error("Detected cycle in desired paths")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- We've detected a move cycle (e.g. MOVE /a -> /b + MOVE /b -> /a)
|
-- We've detected a move cycle (e.g. MOVE /a -> /b + MOVE /b -> /a)
|
||||||
-- Split one of the moves and retry
|
-- Split one of the moves and retry
|
||||||
local intermediate_url =
|
local intermediate_url =
|
||||||
string.format('%s__canola_tmp_%05d', loop_action.src_url, math.random(999999))
|
string.format("%s__oil_tmp_%05d", loop_action.src_url, math.random(999999))
|
||||||
local move_1 = {
|
local move_1 = {
|
||||||
type = 'move',
|
type = "move",
|
||||||
entry_type = loop_action.entry_type,
|
entry_type = loop_action.entry_type,
|
||||||
src_url = loop_action.src_url,
|
src_url = loop_action.src_url,
|
||||||
dest_url = intermediate_url,
|
dest_url = intermediate_url,
|
||||||
}
|
}
|
||||||
local move_2 = {
|
local move_2 = {
|
||||||
type = 'move',
|
type = "move",
|
||||||
entry_type = loop_action.entry_type,
|
entry_type = loop_action.entry_type,
|
||||||
src_url = intermediate_url,
|
src_url = intermediate_url,
|
||||||
dest_url = loop_action.dest_url,
|
dest_url = loop_action.dest_url,
|
||||||
|
|
@ -340,16 +340,16 @@ M.enforce_action_order = function(actions)
|
||||||
dest_trie:insert_action(move_1.dest_url, move_1)
|
dest_trie:insert_action(move_1.dest_url, move_1)
|
||||||
src_trie:insert_action(move_1.src_url, move_1)
|
src_trie:insert_action(move_1.src_url, move_1)
|
||||||
else
|
else
|
||||||
error('Detected cycle in desired paths')
|
error("Detected cycle in desired paths")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if selected then
|
if selected then
|
||||||
if selected.type == 'move' or selected.type == 'copy' then
|
if selected.type == "move" or selected.type == "copy" then
|
||||||
if vim.startswith(selected.dest_url, selected.src_url .. '/') then
|
if vim.startswith(selected.dest_url, selected.src_url .. "/") then
|
||||||
error(
|
error(
|
||||||
string.format(
|
string.format(
|
||||||
'Cannot move or copy parent into itself: %s -> %s',
|
"Cannot move or copy parent into itself: %s -> %s",
|
||||||
selected.src_url,
|
selected.src_url,
|
||||||
selected.dest_url
|
selected.dest_url
|
||||||
)
|
)
|
||||||
|
|
@ -360,9 +360,9 @@ M.enforce_action_order = function(actions)
|
||||||
end
|
end
|
||||||
|
|
||||||
if to_remove then
|
if to_remove then
|
||||||
if to_remove.type == 'delete' or to_remove.type == 'change' then
|
if to_remove.type == "delete" or to_remove.type == "change" then
|
||||||
src_trie:remove_action(to_remove.url, to_remove)
|
src_trie:remove_action(to_remove.url, to_remove)
|
||||||
elseif to_remove.type == 'create' then
|
elseif to_remove.type == "create" then
|
||||||
dest_trie:remove_action(to_remove.url, to_remove)
|
dest_trie:remove_action(to_remove.url, to_remove)
|
||||||
else
|
else
|
||||||
dest_trie:remove_action(to_remove.dest_url, to_remove)
|
dest_trie:remove_action(to_remove.dest_url, to_remove)
|
||||||
|
|
@ -383,12 +383,12 @@ end
|
||||||
|
|
||||||
local progress
|
local progress
|
||||||
|
|
||||||
---@param actions canola.Action[]
|
---@param actions oil.Action[]
|
||||||
---@param cb fun(err: nil|string)
|
---@param cb fun(err: nil|string)
|
||||||
M.process_actions = function(actions, cb)
|
M.process_actions = function(actions, cb)
|
||||||
vim.api.nvim_exec_autocmds(
|
vim.api.nvim_exec_autocmds(
|
||||||
'User',
|
"User",
|
||||||
{ pattern = 'CanolaActionsPre', modeline = false, data = { actions = actions } }
|
{ pattern = "OilActionsPre", modeline = false, data = { actions = actions } }
|
||||||
)
|
)
|
||||||
|
|
||||||
local did_complete = nil
|
local did_complete = nil
|
||||||
|
|
@ -398,14 +398,14 @@ M.process_actions = function(actions, cb)
|
||||||
|
|
||||||
-- Convert some cross-adapter moves to a copy + delete
|
-- Convert some cross-adapter moves to a copy + delete
|
||||||
for _, action in ipairs(actions) do
|
for _, action in ipairs(actions) do
|
||||||
if action.type == 'move' then
|
if action.type == "move" then
|
||||||
local _, cross_action = util.get_adapter_for_action(action)
|
local _, cross_action = util.get_adapter_for_action(action)
|
||||||
-- Only do the conversion if the cross-adapter support is "copy"
|
-- Only do the conversion if the cross-adapter support is "copy"
|
||||||
if cross_action == 'copy' then
|
if cross_action == "copy" then
|
||||||
---@diagnostic disable-next-line: assign-type-mismatch
|
---@diagnostic disable-next-line: assign-type-mismatch
|
||||||
action.type = 'copy'
|
action.type = "copy"
|
||||||
table.insert(actions, {
|
table.insert(actions, {
|
||||||
type = 'delete',
|
type = "delete",
|
||||||
url = action.src_url,
|
url = action.src_url,
|
||||||
entry_type = action.entry_type,
|
entry_type = action.entry_type,
|
||||||
})
|
})
|
||||||
|
|
@ -420,24 +420,9 @@ M.process_actions = function(actions, cb)
|
||||||
finished = true
|
finished = true
|
||||||
progress:close()
|
progress:close()
|
||||||
progress = nil
|
progress = nil
|
||||||
if config.cleanup_buffers_on_delete and not err then
|
|
||||||
for _, action in ipairs(actions) do
|
|
||||||
if action.type == 'delete' then
|
|
||||||
local scheme, path = util.parse_url(action.url)
|
|
||||||
if config.adapters[scheme] == 'files' then
|
|
||||||
assert(path)
|
|
||||||
local os_path = fs.posix_to_os_path(path)
|
|
||||||
local bufnr = vim.fn.bufnr(os_path)
|
|
||||||
if bufnr ~= -1 then
|
|
||||||
vim.api.nvim_buf_delete(bufnr, { force = true })
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
vim.api.nvim_exec_autocmds(
|
vim.api.nvim_exec_autocmds(
|
||||||
'User',
|
"User",
|
||||||
{ pattern = 'CanolaActionsPost', modeline = false, data = { err = err, actions = actions } }
|
{ pattern = "OilActionsPost", modeline = false, data = { err = err, actions = actions } }
|
||||||
)
|
)
|
||||||
cb(err)
|
cb(err)
|
||||||
end
|
end
|
||||||
|
|
@ -450,7 +435,7 @@ M.process_actions = function(actions, cb)
|
||||||
-- TODO some actions are actually cancelable.
|
-- TODO some actions are actually cancelable.
|
||||||
-- We should stop them instead of stopping after the current action
|
-- We should stop them instead of stopping after the current action
|
||||||
cancel = function()
|
cancel = function()
|
||||||
finish('Canceled')
|
finish("Canceled")
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
@ -487,8 +472,8 @@ M.process_actions = function(actions, cb)
|
||||||
next_action()
|
next_action()
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
if action.type == 'change' then
|
if action.type == "change" then
|
||||||
---@cast action canola.ChangeAction
|
---@cast action oil.ChangeAction
|
||||||
columns.perform_change_action(adapter, action, callback)
|
columns.perform_change_action(adapter, action, callback)
|
||||||
else
|
else
|
||||||
adapter.perform_action(action, callback)
|
adapter.perform_action(action, callback)
|
||||||
|
|
@ -517,14 +502,14 @@ M.try_write_changes = function(confirm, cb)
|
||||||
cb = function(_err) end
|
cb = function(_err) end
|
||||||
end
|
end
|
||||||
if mutation_in_progress then
|
if mutation_in_progress then
|
||||||
cb('Cannot perform mutation when already in progress')
|
cb("Cannot perform mutation when already in progress")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local current_buf = vim.api.nvim_get_current_buf()
|
local current_buf = vim.api.nvim_get_current_buf()
|
||||||
local was_modified = vim.bo.modified
|
local was_modified = vim.bo.modified
|
||||||
local buffers = view.get_all_buffers()
|
local buffers = view.get_all_buffers()
|
||||||
local all_diffs = {}
|
local all_diffs = {}
|
||||||
---@type table<integer, canola.ParseError[]>
|
---@type table<integer, oil.ParseError[]>
|
||||||
local all_errors = {}
|
local all_errors = {}
|
||||||
|
|
||||||
mutation_in_progress = true
|
mutation_in_progress = true
|
||||||
|
|
@ -552,7 +537,7 @@ M.try_write_changes = function(confirm, cb)
|
||||||
mutation_in_progress = false
|
mutation_in_progress = false
|
||||||
end
|
end
|
||||||
|
|
||||||
local ns = vim.api.nvim_create_namespace('Canola')
|
local ns = vim.api.nvim_create_namespace("Oil")
|
||||||
vim.diagnostic.reset(ns)
|
vim.diagnostic.reset(ns)
|
||||||
if not vim.tbl_isempty(all_errors) then
|
if not vim.tbl_isempty(all_errors) then
|
||||||
for bufnr, errors in pairs(all_errors) do
|
for bufnr, errors in pairs(all_errors) do
|
||||||
|
|
@ -579,7 +564,7 @@ M.try_write_changes = function(confirm, cb)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
unlock()
|
unlock()
|
||||||
cb('Error parsing canola buffers')
|
cb("Error parsing oil buffers")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -587,7 +572,7 @@ M.try_write_changes = function(confirm, cb)
|
||||||
confirmation.show(actions, confirm, function(proceed)
|
confirmation.show(actions, confirm, function(proceed)
|
||||||
if not proceed then
|
if not proceed then
|
||||||
unlock()
|
unlock()
|
||||||
cb('Canceled')
|
cb("Canceled")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -596,23 +581,23 @@ M.try_write_changes = function(confirm, cb)
|
||||||
vim.schedule_wrap(function(err)
|
vim.schedule_wrap(function(err)
|
||||||
view.unlock_buffers()
|
view.unlock_buffers()
|
||||||
if err then
|
if err then
|
||||||
err = string.format('[canola] Error applying actions: %s', err)
|
err = string.format("[oil] Error applying actions: %s", err)
|
||||||
view.rerender_all_canola_buffers(nil, function()
|
view.rerender_all_oil_buffers(nil, function()
|
||||||
cb(err)
|
cb(err)
|
||||||
end)
|
end)
|
||||||
else
|
else
|
||||||
local current_entry = canola.get_cursor_entry()
|
local current_entry = oil.get_cursor_entry()
|
||||||
if current_entry then
|
if current_entry then
|
||||||
-- get the entry under the cursor and make sure the cursor stays on it
|
-- get the entry under the cursor and make sure the cursor stays on it
|
||||||
view.set_last_cursor(
|
view.set_last_cursor(
|
||||||
vim.api.nvim_buf_get_name(0),
|
vim.api.nvim_buf_get_name(0),
|
||||||
vim.split(current_entry.parsed_name or current_entry.name, '/')[1]
|
vim.split(current_entry.parsed_name or current_entry.name, "/")[1]
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
view.rerender_all_canola_buffers(nil, function(render_err)
|
view.rerender_all_oil_buffers(nil, function(render_err)
|
||||||
vim.api.nvim_exec_autocmds(
|
vim.api.nvim_exec_autocmds(
|
||||||
'User',
|
"User",
|
||||||
{ pattern = 'CanolaMutationComplete', modeline = false }
|
{ pattern = "OilMutationComplete", modeline = false }
|
||||||
)
|
)
|
||||||
cb(render_err)
|
cb(render_err)
|
||||||
end)
|
end)
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
local cache = require('canola.cache')
|
local cache = require("oil.cache")
|
||||||
local columns = require('canola.columns')
|
local columns = require("oil.columns")
|
||||||
local config = require('canola.config')
|
local config = require("oil.config")
|
||||||
local constants = require('canola.constants')
|
local constants = require("oil.constants")
|
||||||
local fs = require('canola.fs')
|
local fs = require("oil.fs")
|
||||||
local util = require('canola.util')
|
local util = require("oil.util")
|
||||||
local view = require('canola.view')
|
local view = require("oil.view")
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local FIELD_ID = constants.FIELD_ID
|
local FIELD_ID = constants.FIELD_ID
|
||||||
|
|
@ -12,23 +12,23 @@ local FIELD_NAME = constants.FIELD_NAME
|
||||||
local FIELD_TYPE = constants.FIELD_TYPE
|
local FIELD_TYPE = constants.FIELD_TYPE
|
||||||
local FIELD_META = constants.FIELD_META
|
local FIELD_META = constants.FIELD_META
|
||||||
|
|
||||||
---@alias canola.Diff canola.DiffNew|canola.DiffDelete|canola.DiffChange
|
---@alias oil.Diff oil.DiffNew|oil.DiffDelete|oil.DiffChange
|
||||||
|
|
||||||
---@class (exact) canola.DiffNew
|
---@class (exact) oil.DiffNew
|
||||||
---@field type "new"
|
---@field type "new"
|
||||||
---@field name string
|
---@field name string
|
||||||
---@field entry_type canola.EntryType
|
---@field entry_type oil.EntryType
|
||||||
---@field id nil|integer
|
---@field id nil|integer
|
||||||
---@field link nil|string
|
---@field link nil|string
|
||||||
|
|
||||||
---@class (exact) canola.DiffDelete
|
---@class (exact) oil.DiffDelete
|
||||||
---@field type "delete"
|
---@field type "delete"
|
||||||
---@field name string
|
---@field name string
|
||||||
---@field id integer
|
---@field id integer
|
||||||
|
|
||||||
---@class (exact) canola.DiffChange
|
---@class (exact) oil.DiffChange
|
||||||
---@field type "change"
|
---@field type "change"
|
||||||
---@field entry_type canola.EntryType
|
---@field entry_type oil.EntryType
|
||||||
---@field name string
|
---@field name string
|
||||||
---@field column string
|
---@field column string
|
||||||
---@field value any
|
---@field value any
|
||||||
|
|
@ -37,7 +37,7 @@ local FIELD_META = constants.FIELD_META
|
||||||
---@return string
|
---@return string
|
||||||
---@return boolean
|
---@return boolean
|
||||||
local function parsedir(name)
|
local function parsedir(name)
|
||||||
local isdir = vim.endswith(name, '/') or (fs.is_windows and vim.endswith(name, '\\'))
|
local isdir = vim.endswith(name, "/") or (fs.is_windows and vim.endswith(name, "\\"))
|
||||||
if isdir then
|
if isdir then
|
||||||
name = name:sub(1, name:len() - 1)
|
name = name:sub(1, name:len() - 1)
|
||||||
end
|
end
|
||||||
|
|
@ -52,29 +52,29 @@ local function compare_link_target(meta, parsed_entry)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
-- Make sure we trim off any trailing path slashes from both sources
|
-- Make sure we trim off any trailing path slashes from both sources
|
||||||
local meta_name = meta.link:gsub('[/\\]$', '')
|
local meta_name = meta.link:gsub("[/\\]$", "")
|
||||||
local parsed_name = parsed_entry.link_target:gsub('[/\\]$', '')
|
local parsed_name = parsed_entry.link_target:gsub("[/\\]$", "")
|
||||||
return meta_name == parsed_name
|
return meta_name == parsed_name
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class (exact) canola.ParseResult
|
---@class (exact) oil.ParseResult
|
||||||
---@field data table Parsed entry data
|
---@field data table Parsed entry data
|
||||||
---@field ranges table<string, integer[]> Locations of the various columns
|
---@field ranges table<string, integer[]> Locations of the various columns
|
||||||
---@field entry nil|canola.InternalEntry If the entry already exists
|
---@field entry nil|oil.InternalEntry If the entry already exists
|
||||||
|
|
||||||
---Parse a single line in a buffer
|
---Parse a single line in a buffer
|
||||||
---@param adapter canola.Adapter
|
---@param adapter oil.Adapter
|
||||||
---@param line string
|
---@param line string
|
||||||
---@param column_defs canola.ColumnSpec[]
|
---@param column_defs oil.ColumnSpec[]
|
||||||
---@return nil|canola.ParseResult
|
---@return nil|oil.ParseResult
|
||||||
---@return nil|string Error
|
---@return nil|string Error
|
||||||
M.parse_line = function(adapter, line, column_defs)
|
M.parse_line = function(adapter, line, column_defs)
|
||||||
local ret = {}
|
local ret = {}
|
||||||
local ranges = {}
|
local ranges = {}
|
||||||
local start = 1
|
local start = 1
|
||||||
local value, rem = line:match('^/(%d+) (.+)$')
|
local value, rem = line:match("^/(%d+) (.+)$")
|
||||||
if not value then
|
if not value then
|
||||||
return nil, 'Malformed ID at start of line'
|
return nil, "Malformed ID at start of line"
|
||||||
end
|
end
|
||||||
ranges.id = { start, value:len() + 1 }
|
ranges.id = { start, value:len() + 1 }
|
||||||
start = ranges.id[2] + 1
|
start = ranges.id[2] + 1
|
||||||
|
|
@ -97,7 +97,7 @@ M.parse_line = function(adapter, line, column_defs)
|
||||||
local start_len = string.len(rem)
|
local start_len = string.len(rem)
|
||||||
value, rem = columns.parse_col(adapter, assert(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
|
||||||
ret[name] = value
|
ret[name] = value
|
||||||
range[2] = range[1] + start_len - string.len(rem) - 1
|
range[2] = range[1] + start_len - string.len(rem) - 1
|
||||||
|
|
@ -108,10 +108,10 @@ M.parse_line = function(adapter, line, column_defs)
|
||||||
if name then
|
if name then
|
||||||
local isdir
|
local isdir
|
||||||
name, isdir = parsedir(vim.trim(name))
|
name, isdir = parsedir(vim.trim(name))
|
||||||
if name ~= '' then
|
if name ~= "" then
|
||||||
ret.name = name
|
ret.name = name
|
||||||
end
|
end
|
||||||
ret._type = isdir and 'directory' or 'file'
|
ret._type = isdir and "directory" or "file"
|
||||||
end
|
end
|
||||||
local entry = cache.get_entry_by_id(ret.id)
|
local entry = cache.get_entry_by_id(ret.id)
|
||||||
ranges.name = { start, start + string.len(rem) - 1 }
|
ranges.name = { start, start + string.len(rem) - 1 }
|
||||||
|
|
@ -122,38 +122,38 @@ M.parse_line = function(adapter, line, column_defs)
|
||||||
-- Parse the symlink syntax
|
-- Parse the symlink syntax
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
local entry_type = entry[FIELD_TYPE]
|
local entry_type = entry[FIELD_TYPE]
|
||||||
if entry_type == 'link' and meta and meta.link then
|
if entry_type == "link" and meta and meta.link then
|
||||||
local name_pieces = vim.split(ret.name, ' -> ', { plain = true })
|
local name_pieces = vim.split(ret.name, " -> ", { plain = true })
|
||||||
if #name_pieces ~= 2 then
|
if #name_pieces ~= 2 then
|
||||||
ret.name = ''
|
ret.name = ""
|
||||||
return { data = ret, ranges = ranges }
|
return { data = ret, ranges = ranges }
|
||||||
end
|
end
|
||||||
ranges.name = { start, start + string.len(name_pieces[1]) - 1 }
|
ranges.name = { start, start + string.len(name_pieces[1]) - 1 }
|
||||||
ret.name = parsedir(vim.trim(name_pieces[1]))
|
ret.name = parsedir(vim.trim(name_pieces[1]))
|
||||||
ret.link_target = name_pieces[2]
|
ret.link_target = name_pieces[2]
|
||||||
ret._type = 'link'
|
ret._type = "link"
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Try to keep the same file type
|
-- Try to keep the same file type
|
||||||
if entry_type ~= 'directory' and entry_type ~= 'file' and ret._type ~= 'directory' then
|
if entry_type ~= "directory" and entry_type ~= "file" and ret._type ~= "directory" then
|
||||||
ret._type = entry[FIELD_TYPE]
|
ret._type = entry[FIELD_TYPE]
|
||||||
end
|
end
|
||||||
|
|
||||||
return { data = ret, entry = entry, ranges = ranges }
|
return { data = ret, entry = entry, ranges = ranges }
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class (exact) canola.ParseError
|
---@class (exact) oil.ParseError
|
||||||
---@field lnum integer
|
---@field lnum integer
|
||||||
---@field col integer
|
---@field col integer
|
||||||
---@field message string
|
---@field message string
|
||||||
|
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
---@return canola.Diff[] diffs
|
---@return oil.Diff[] diffs
|
||||||
---@return canola.ParseError[] errors Parsing errors
|
---@return oil.ParseError[] errors Parsing errors
|
||||||
M.parse = function(bufnr)
|
M.parse = function(bufnr)
|
||||||
---@type canola.Diff[]
|
---@type oil.Diff[]
|
||||||
local diffs = {}
|
local diffs = {}
|
||||||
---@type canola.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, true)
|
local adapter = util.get_adapter(bufnr, true)
|
||||||
|
|
@ -187,7 +187,7 @@ M.parse = function(bufnr)
|
||||||
name = name:lower()
|
name = name:lower()
|
||||||
end
|
end
|
||||||
if seen_names[name] then
|
if seen_names[name] then
|
||||||
table.insert(errors, { message = 'Duplicate filename', lnum = i - 1, end_lnum = i, col = 0 })
|
table.insert(errors, { message = "Duplicate filename", lnum = i - 1, end_lnum = i, col = 0 })
|
||||||
else
|
else
|
||||||
seen_names[name] = true
|
seen_names[name] = true
|
||||||
end
|
end
|
||||||
|
|
@ -197,7 +197,7 @@ M.parse = function(bufnr)
|
||||||
-- hack to be compatible with Lua 5.1
|
-- hack to be compatible with Lua 5.1
|
||||||
-- use return instead of goto
|
-- use return instead of goto
|
||||||
(function()
|
(function()
|
||||||
if line:match('^/%d+') then
|
if line:match("^/%d+") then
|
||||||
-- Parse the line for an existing entry
|
-- Parse the line for an existing entry
|
||||||
local result, err = M.parse_line(adapter, line, column_defs)
|
local result, err = M.parse_line(adapter, line, column_defs)
|
||||||
if not result or err then
|
if not result or err then
|
||||||
|
|
@ -217,11 +217,11 @@ M.parse = function(bufnr)
|
||||||
|
|
||||||
local err_message
|
local err_message
|
||||||
if not parsed_entry.name then
|
if not parsed_entry.name then
|
||||||
err_message = 'No filename found'
|
err_message = "No filename found"
|
||||||
elseif not entry then
|
elseif not entry then
|
||||||
err_message = 'Could not find existing entry (was the ID changed?)'
|
err_message = "Could not find existing entry (was the ID changed?)"
|
||||||
elseif parsed_entry.name:match('/') or parsed_entry.name:match(fs.sep) then
|
elseif parsed_entry.name:match("/") or parsed_entry.name:match(fs.sep) then
|
||||||
err_message = 'Filename cannot contain path separator'
|
err_message = "Filename cannot contain path separator"
|
||||||
end
|
end
|
||||||
if err_message then
|
if err_message then
|
||||||
table.insert(errors, {
|
table.insert(errors, {
|
||||||
|
|
@ -237,16 +237,16 @@ M.parse = function(bufnr)
|
||||||
check_dupe(parsed_entry.name, i)
|
check_dupe(parsed_entry.name, i)
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
if original_entries[parsed_entry.name] == parsed_entry.id then
|
if original_entries[parsed_entry.name] == parsed_entry.id then
|
||||||
if entry[FIELD_TYPE] == 'link' and not compare_link_target(meta, parsed_entry) then
|
if entry[FIELD_TYPE] == "link" and not compare_link_target(meta, parsed_entry) then
|
||||||
table.insert(diffs, {
|
table.insert(diffs, {
|
||||||
type = 'new',
|
type = "new",
|
||||||
name = parsed_entry.name,
|
name = parsed_entry.name,
|
||||||
entry_type = 'link',
|
entry_type = "link",
|
||||||
link = parsed_entry.link_target,
|
link = parsed_entry.link_target,
|
||||||
})
|
})
|
||||||
elseif entry[FIELD_TYPE] ~= parsed_entry._type then
|
elseif entry[FIELD_TYPE] ~= parsed_entry._type then
|
||||||
table.insert(diffs, {
|
table.insert(diffs, {
|
||||||
type = 'new',
|
type = "new",
|
||||||
name = parsed_entry.name,
|
name = parsed_entry.name,
|
||||||
entry_type = parsed_entry._type,
|
entry_type = parsed_entry._type,
|
||||||
})
|
})
|
||||||
|
|
@ -255,7 +255,7 @@ M.parse = function(bufnr)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
table.insert(diffs, {
|
table.insert(diffs, {
|
||||||
type = 'new',
|
type = "new",
|
||||||
name = parsed_entry.name,
|
name = parsed_entry.name,
|
||||||
entry_type = parsed_entry._type,
|
entry_type = parsed_entry._type,
|
||||||
id = parsed_entry.id,
|
id = parsed_entry.id,
|
||||||
|
|
@ -267,7 +267,7 @@ M.parse = function(bufnr)
|
||||||
local col_name = util.split_config(col_def)
|
local col_name = util.split_config(col_def)
|
||||||
if columns.compare(adapter, col_name, entry, parsed_entry[col_name]) then
|
if columns.compare(adapter, col_name, entry, parsed_entry[col_name]) then
|
||||||
table.insert(diffs, {
|
table.insert(diffs, {
|
||||||
type = 'change',
|
type = "change",
|
||||||
name = parsed_entry.name,
|
name = parsed_entry.name,
|
||||||
entry_type = entry[FIELD_TYPE],
|
entry_type = entry[FIELD_TYPE],
|
||||||
column = col_name,
|
column = col_name,
|
||||||
|
|
@ -278,7 +278,7 @@ M.parse = function(bufnr)
|
||||||
else
|
else
|
||||||
-- Parse a new entry
|
-- Parse a new entry
|
||||||
local name, isdir = parsedir(vim.trim(line))
|
local name, isdir = parsedir(vim.trim(line))
|
||||||
if vim.startswith(name, '/') then
|
if vim.startswith(name, "/") then
|
||||||
table.insert(errors, {
|
table.insert(errors, {
|
||||||
message = "Paths cannot start with '/'",
|
message = "Paths cannot start with '/'",
|
||||||
lnum = i - 1,
|
lnum = i - 1,
|
||||||
|
|
@ -287,17 +287,17 @@ M.parse = function(bufnr)
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
if name ~= '' then
|
if name ~= "" then
|
||||||
local link_pieces = vim.split(name, ' -> ', { plain = true })
|
local link_pieces = vim.split(name, " -> ", { plain = true })
|
||||||
local entry_type = isdir and 'directory' or 'file'
|
local entry_type = isdir and "directory" or "file"
|
||||||
local link
|
local link
|
||||||
if #link_pieces == 2 then
|
if #link_pieces == 2 then
|
||||||
entry_type = 'link'
|
entry_type = "link"
|
||||||
name, link = unpack(link_pieces)
|
name, link = unpack(link_pieces)
|
||||||
end
|
end
|
||||||
check_dupe(name, i)
|
check_dupe(name, i)
|
||||||
table.insert(diffs, {
|
table.insert(diffs, {
|
||||||
type = 'new',
|
type = "new",
|
||||||
name = name,
|
name = name,
|
||||||
entry_type = entry_type,
|
entry_type = entry_type,
|
||||||
link = link,
|
link = link,
|
||||||
|
|
@ -309,7 +309,7 @@ M.parse = function(bufnr)
|
||||||
|
|
||||||
for name, child_id in pairs(original_entries) do
|
for name, child_id in pairs(original_entries) do
|
||||||
table.insert(diffs, {
|
table.insert(diffs, {
|
||||||
type = 'delete',
|
type = "delete",
|
||||||
name = name,
|
name = name,
|
||||||
id = child_id,
|
id = child_id,
|
||||||
})
|
})
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
local columns = require('canola.columns')
|
local columns = require("oil.columns")
|
||||||
local config = require('canola.config')
|
local config = require("oil.config")
|
||||||
local layout = require('canola.layout')
|
local layout = require("oil.layout")
|
||||||
local loading = require('canola.loading')
|
local loading = require("oil.loading")
|
||||||
local util = require('canola.util')
|
local util = require("oil.util")
|
||||||
local Progress = {}
|
local Progress = {}
|
||||||
|
|
||||||
local FPS = 20
|
local FPS = 20
|
||||||
|
|
||||||
function Progress.new()
|
function Progress.new()
|
||||||
return setmetatable({
|
return setmetatable({
|
||||||
lines = { '', '' },
|
lines = { "", "" },
|
||||||
count = '',
|
count = "",
|
||||||
spinner = '',
|
spinner = "",
|
||||||
bufnr = nil,
|
bufnr = nil,
|
||||||
winid = nil,
|
winid = nil,
|
||||||
min_bufnr = nil,
|
min_bufnr = nil,
|
||||||
|
|
@ -40,18 +40,18 @@ function Progress:show(opts)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||||
vim.bo[bufnr].bufhidden = 'wipe'
|
vim.bo[bufnr].bufhidden = "wipe"
|
||||||
self.bufnr = bufnr
|
self.bufnr = bufnr
|
||||||
self.cancel = opts.cancel or self.cancel
|
self.cancel = opts.cancel or self.cancel
|
||||||
local loading_iter = loading.get_bar_iter()
|
local loading_iter = loading.get_bar_iter()
|
||||||
local spinner = loading.get_iter('dots')
|
local spinner = loading.get_iter("dots")
|
||||||
if not self.timer then
|
if not self.timer then
|
||||||
self.timer = vim.loop.new_timer()
|
self.timer = vim.loop.new_timer()
|
||||||
self.timer:start(
|
self.timer:start(
|
||||||
0,
|
0,
|
||||||
math.floor(1000 / FPS),
|
math.floor(1000 / FPS),
|
||||||
vim.schedule_wrap(function()
|
vim.schedule_wrap(function()
|
||||||
self.lines[2] = string.format('%s %s', self.count, loading_iter())
|
self.lines[2] = string.format("%s %s", self.count, loading_iter())
|
||||||
self.spinner = spinner()
|
self.spinner = spinner()
|
||||||
self:_render()
|
self:_render()
|
||||||
end)
|
end)
|
||||||
|
|
@ -59,22 +59,22 @@ function Progress:show(opts)
|
||||||
end
|
end
|
||||||
local width, height = layout.calculate_dims(120, 10, config.progress)
|
local width, height = layout.calculate_dims(120, 10, config.progress)
|
||||||
self.winid = vim.api.nvim_open_win(self.bufnr, true, {
|
self.winid = vim.api.nvim_open_win(self.bufnr, true, {
|
||||||
relative = 'editor',
|
relative = "editor",
|
||||||
width = width,
|
width = width,
|
||||||
height = height,
|
height = height,
|
||||||
row = math.floor((layout.get_editor_height() - height) / 2),
|
row = math.floor((layout.get_editor_height() - height) / 2),
|
||||||
col = math.floor((layout.get_editor_width() - width) / 2),
|
col = math.floor((layout.get_editor_width() - width) / 2),
|
||||||
zindex = 152, -- render on top of the floating window title
|
zindex = 152, -- render on top of the floating window title
|
||||||
style = 'minimal',
|
style = "minimal",
|
||||||
border = config.progress.border,
|
border = config.progress.border,
|
||||||
})
|
})
|
||||||
vim.bo[self.bufnr].filetype = 'canola_progress'
|
vim.bo[self.bufnr].filetype = "oil_progress"
|
||||||
for k, v in pairs(config.progress.win_options) do
|
for k, v in pairs(config.progress.win_options) do
|
||||||
vim.api.nvim_set_option_value(k, v, { scope = 'local', win = self.winid })
|
vim.api.nvim_set_option_value(k, v, { scope = "local", win = self.winid })
|
||||||
end
|
end
|
||||||
table.insert(
|
table.insert(
|
||||||
self.autocmds,
|
self.autocmds,
|
||||||
vim.api.nvim_create_autocmd('VimResized', {
|
vim.api.nvim_create_autocmd("VimResized", {
|
||||||
callback = function()
|
callback = function()
|
||||||
self:_reposition()
|
self:_reposition()
|
||||||
end,
|
end,
|
||||||
|
|
@ -82,7 +82,7 @@ function Progress:show(opts)
|
||||||
)
|
)
|
||||||
table.insert(
|
table.insert(
|
||||||
self.autocmds,
|
self.autocmds,
|
||||||
vim.api.nvim_create_autocmd('WinLeave', {
|
vim.api.nvim_create_autocmd("WinLeave", {
|
||||||
callback = function()
|
callback = function()
|
||||||
self:minimize()
|
self:minimize()
|
||||||
end,
|
end,
|
||||||
|
|
@ -94,17 +94,17 @@ function Progress:show(opts)
|
||||||
vim.api.nvim_win_close(self.winid, true)
|
vim.api.nvim_win_close(self.winid, true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
vim.keymap.set('n', 'c', cancel, { buffer = self.bufnr, nowait = true })
|
vim.keymap.set("n", "c", cancel, { buffer = self.bufnr, nowait = true })
|
||||||
vim.keymap.set('n', 'C', cancel, { buffer = self.bufnr, nowait = true })
|
vim.keymap.set("n", "C", cancel, { buffer = self.bufnr, nowait = true })
|
||||||
vim.keymap.set('n', 'm', minimize, { buffer = self.bufnr, nowait = true })
|
vim.keymap.set("n", "m", minimize, { buffer = self.bufnr, nowait = true })
|
||||||
vim.keymap.set('n', 'M', minimize, { buffer = self.bufnr, nowait = true })
|
vim.keymap.set("n", "M", minimize, { buffer = self.bufnr, nowait = true })
|
||||||
end
|
end
|
||||||
|
|
||||||
function Progress:restore()
|
function Progress:restore()
|
||||||
if self.closing then
|
if self.closing then
|
||||||
return
|
return
|
||||||
elseif not self:is_minimized() then
|
elseif not self:is_minimized() then
|
||||||
error('Cannot restore progress window: not minimized')
|
error("Cannot restore progress window: not minimized")
|
||||||
end
|
end
|
||||||
self:_cleanup_minimized_win()
|
self:_cleanup_minimized_win()
|
||||||
self:show()
|
self:show()
|
||||||
|
|
@ -115,14 +115,14 @@ function Progress:_render()
|
||||||
util.render_text(
|
util.render_text(
|
||||||
self.bufnr,
|
self.bufnr,
|
||||||
self.lines,
|
self.lines,
|
||||||
{ winid = self.winid, actions = { '[M]inimize', '[C]ancel' } }
|
{ winid = self.winid, actions = { "[M]inimize", "[C]ancel" } }
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
if self.min_bufnr and vim.api.nvim_buf_is_valid(self.min_bufnr) then
|
if self.min_bufnr and vim.api.nvim_buf_is_valid(self.min_bufnr) then
|
||||||
util.render_text(
|
util.render_text(
|
||||||
self.min_bufnr,
|
self.min_bufnr,
|
||||||
{ string.format('%sCanola: %s', self.spinner, self.count) },
|
{ string.format("%sOil: %s", self.spinner, self.count) },
|
||||||
{ winid = self.min_winid, h_align = 'left' }
|
{ winid = self.min_winid, h_align = "left" }
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -136,7 +136,7 @@ function Progress:_reposition()
|
||||||
end
|
end
|
||||||
local width, height = layout.calculate_dims(min_width, 10, config.progress)
|
local width, height = layout.calculate_dims(min_width, 10, config.progress)
|
||||||
vim.api.nvim_win_set_config(self.winid, {
|
vim.api.nvim_win_set_config(self.winid, {
|
||||||
relative = 'editor',
|
relative = "editor",
|
||||||
width = width,
|
width = width,
|
||||||
height = height,
|
height = height,
|
||||||
row = math.floor((layout.get_editor_height() - height) / 2),
|
row = math.floor((layout.get_editor_height() - height) / 2),
|
||||||
|
|
@ -174,38 +174,38 @@ function Progress:minimize()
|
||||||
end
|
end
|
||||||
self:_cleanup_main_win()
|
self:_cleanup_main_win()
|
||||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||||
vim.bo[bufnr].bufhidden = 'wipe'
|
vim.bo[bufnr].bufhidden = "wipe"
|
||||||
local winid = vim.api.nvim_open_win(bufnr, false, {
|
local winid = vim.api.nvim_open_win(bufnr, false, {
|
||||||
relative = 'editor',
|
relative = "editor",
|
||||||
width = 16,
|
width = 16,
|
||||||
height = 1,
|
height = 1,
|
||||||
anchor = 'SE',
|
anchor = "SE",
|
||||||
row = layout.get_editor_height(),
|
row = layout.get_editor_height(),
|
||||||
col = layout.get_editor_width(),
|
col = layout.get_editor_width(),
|
||||||
zindex = 152, -- render on top of the floating window title
|
zindex = 152, -- render on top of the floating window title
|
||||||
style = 'minimal',
|
style = "minimal",
|
||||||
border = config.progress.minimized_border,
|
border = config.progress.minimized_border,
|
||||||
})
|
})
|
||||||
self.min_bufnr = bufnr
|
self.min_bufnr = bufnr
|
||||||
self.min_winid = winid
|
self.min_winid = winid
|
||||||
self:_render()
|
self:_render()
|
||||||
vim.notify_once('Restore progress window with :Canola --progress')
|
vim.notify_once("Restore progress window with :Oil --progress")
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param action canola.Action
|
---@param action oil.Action
|
||||||
---@param idx integer
|
---@param idx integer
|
||||||
---@param total integer
|
---@param total integer
|
||||||
function Progress:set_action(action, idx, total)
|
function Progress:set_action(action, idx, total)
|
||||||
local adapter = util.get_adapter_for_action(action)
|
local adapter = util.get_adapter_for_action(action)
|
||||||
local change_line
|
local change_line
|
||||||
if action.type == 'change' then
|
if action.type == "change" then
|
||||||
---@cast action canola.ChangeAction
|
---@cast action oil.ChangeAction
|
||||||
change_line = columns.render_change_action(adapter, action)
|
change_line = columns.render_change_action(adapter, action)
|
||||||
else
|
else
|
||||||
change_line = adapter.render_action(action)
|
change_line = adapter.render_action(action)
|
||||||
end
|
end
|
||||||
self.lines[1] = change_line
|
self.lines[1] = change_line
|
||||||
self.count = string.format('%d/%d', idx, total)
|
self.count = string.format("%d/%d", idx, total)
|
||||||
self:_reposition()
|
self:_reposition()
|
||||||
self:_render()
|
self:_render()
|
||||||
end
|
end
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
local util = require('canola.util')
|
local util = require("oil.util")
|
||||||
|
|
||||||
---@class (exact) canola.Trie
|
---@class (exact) oil.Trie
|
||||||
---@field new fun(): canola.Trie
|
---@field new fun(): oil.Trie
|
||||||
---@field private root table
|
---@field private root table
|
||||||
local Trie = {}
|
local Trie = {}
|
||||||
|
|
||||||
---@return canola.Trie
|
---@return oil.Trie
|
||||||
Trie.new = function()
|
Trie.new = function()
|
||||||
---@type canola.Trie
|
---@type oil.Trie
|
||||||
return setmetatable({
|
return setmetatable({
|
||||||
root = { values = {}, children = {} },
|
root = { values = {}, children = {} },
|
||||||
}, {
|
}, {
|
||||||
|
|
@ -20,7 +20,7 @@ end
|
||||||
function Trie:_url_to_path_pieces(url)
|
function Trie:_url_to_path_pieces(url)
|
||||||
local scheme, path = util.parse_url(url)
|
local scheme, path = util.parse_url(url)
|
||||||
assert(path)
|
assert(path)
|
||||||
local pieces = vim.split(path, '/')
|
local pieces = vim.split(path, "/")
|
||||||
table.insert(pieces, 1, scheme)
|
table.insert(pieces, 1, scheme)
|
||||||
return pieces
|
return pieces
|
||||||
end
|
end
|
||||||
|
|
@ -75,12 +75,12 @@ function Trie:remove(path_pieces, value)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
error('Value not present in trie: ' .. vim.inspect(value))
|
error("Value not present in trie: " .. vim.inspect(value))
|
||||||
end
|
end
|
||||||
|
|
||||||
---Add the first action that affects a parent path of the url
|
---Add the first action that affects a parent path of the url
|
||||||
---@param url string
|
---@param url string
|
||||||
---@param ret canola.InternalEntry[]
|
---@param ret oil.InternalEntry[]
|
||||||
function Trie:accum_first_parents_of(url, ret)
|
function Trie:accum_first_parents_of(url, ret)
|
||||||
local pieces = self:_url_to_path_pieces(url)
|
local pieces = self:_url_to_path_pieces(url)
|
||||||
local containers = { self.root }
|
local containers = { self.root }
|
||||||
|
|
@ -117,8 +117,8 @@ end
|
||||||
|
|
||||||
---Add all actions affecting children of the url
|
---Add all actions affecting children of the url
|
||||||
---@param url string
|
---@param url string
|
||||||
---@param ret canola.InternalEntry[]
|
---@param ret oil.InternalEntry[]
|
||||||
---@param filter nil|fun(entry: canola.Action): boolean
|
---@param filter nil|fun(entry: oil.Action): boolean
|
||||||
function Trie:accum_children_of(url, ret, filter)
|
function Trie:accum_children_of(url, ret, filter)
|
||||||
local pieces = self:_url_to_path_pieces(url)
|
local pieces = self:_url_to_path_pieces(url)
|
||||||
local current = self.root
|
local current = self.root
|
||||||
|
|
@ -137,8 +137,8 @@ end
|
||||||
|
|
||||||
---Add all actions at a specific path
|
---Add all actions at a specific path
|
||||||
---@param url string
|
---@param url string
|
||||||
---@param ret canola.InternalEntry[]
|
---@param ret oil.InternalEntry[]
|
||||||
---@param filter? fun(entry: canola.Action): boolean
|
---@param filter? fun(entry: oil.Action): boolean
|
||||||
function Trie:accum_actions_at(url, ret, filter)
|
function Trie:accum_actions_at(url, ret, filter)
|
||||||
local pieces = self:_url_to_path_pieces(url)
|
local pieces = self:_url_to_path_pieces(url)
|
||||||
local current = self.root
|
local current = self.root
|
||||||
29
lua/oil/pathutil.lua
Normal file
29
lua/oil/pathutil.lua
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
---@param path string
|
||||||
|
---@return string
|
||||||
|
M.parent = function(path)
|
||||||
|
if path == "/" then
|
||||||
|
return "/"
|
||||||
|
elseif path == "" then
|
||||||
|
return ""
|
||||||
|
elseif vim.endswith(path, "/") then
|
||||||
|
return path:match("^(.*/)[^/]*/$") or ""
|
||||||
|
else
|
||||||
|
return path:match("^(.*/)[^/]*$") or ""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param path string
|
||||||
|
---@return nil|string
|
||||||
|
M.basename = function(path)
|
||||||
|
if path == "/" or path == "" then
|
||||||
|
return
|
||||||
|
elseif vim.endswith(path, "/") then
|
||||||
|
return path:match("^.*/([^/]*)/$")
|
||||||
|
else
|
||||||
|
return path:match("^.*/([^/]*)$")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
---@class canola.Ringbuf
|
---@class oil.Ringbuf
|
||||||
---@field private size integer
|
---@field private size integer
|
||||||
---@field private tail integer
|
---@field private tail integer
|
||||||
---@field private buf string[]
|
---@field private buf string[]
|
||||||
|
|
@ -23,11 +23,11 @@ end
|
||||||
|
|
||||||
---@return string
|
---@return string
|
||||||
function Ringbuf:as_str()
|
function Ringbuf:as_str()
|
||||||
local postfix = ''
|
local postfix = ""
|
||||||
for i = 1, self.tail, 1 do
|
for i = 1, self.tail, 1 do
|
||||||
postfix = postfix .. self.buf[i]
|
postfix = postfix .. self.buf[i]
|
||||||
end
|
end
|
||||||
local prefix = ''
|
local prefix = ""
|
||||||
for i = self.tail + 1, #self.buf, 1 do
|
for i = self.tail + 1, #self.buf, 1 do
|
||||||
prefix = prefix .. self.buf[i]
|
prefix = prefix .. self.buf[i]
|
||||||
end
|
end
|
||||||
|
|
@ -9,7 +9,7 @@ M.run = function(cmd, opts, callback)
|
||||||
local stderr = {}
|
local stderr = {}
|
||||||
local jid = vim.fn.jobstart(
|
local jid = vim.fn.jobstart(
|
||||||
cmd,
|
cmd,
|
||||||
vim.tbl_deep_extend('keep', opts, {
|
vim.tbl_deep_extend("keep", opts, {
|
||||||
stdout_buffered = true,
|
stdout_buffered = true,
|
||||||
stderr_buffered = true,
|
stderr_buffered = true,
|
||||||
on_stdout = function(j, output)
|
on_stdout = function(j, output)
|
||||||
|
|
@ -22,19 +22,19 @@ M.run = function(cmd, opts, callback)
|
||||||
if code == 0 then
|
if code == 0 then
|
||||||
callback(nil, stdout)
|
callback(nil, stdout)
|
||||||
else
|
else
|
||||||
local err = table.concat(stderr, '\n')
|
local err = table.concat(stderr, "\n")
|
||||||
if err == '' then
|
if err == "" then
|
||||||
err = 'Unknown error'
|
err = "Unknown error"
|
||||||
end
|
end
|
||||||
local cmd_str = type(cmd) == 'string' and cmd or table.concat(cmd, ' ')
|
local cmd_str = type(cmd) == "string" and cmd or table.concat(cmd, " ")
|
||||||
callback(string.format("Error running command '%s'\n%s", cmd_str, err))
|
callback(string.format("Error running command '%s'\n%s", cmd_str, err))
|
||||||
end
|
end
|
||||||
end),
|
end),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
local exe
|
local exe
|
||||||
if type(cmd) == 'string' then
|
if type(cmd) == "string" then
|
||||||
exe = vim.split(cmd, '%s+')[1]
|
exe = vim.split(cmd, "%s+")[1]
|
||||||
else
|
else
|
||||||
exe = cmd[1]
|
exe = cmd[1]
|
||||||
end
|
end
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
local config = require('canola.config')
|
local config = require("oil.config")
|
||||||
local constants = require('canola.constants')
|
local constants = require("oil.constants")
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
|
|
@ -8,13 +8,13 @@ local FIELD_NAME = constants.FIELD_NAME
|
||||||
local FIELD_TYPE = constants.FIELD_TYPE
|
local FIELD_TYPE = constants.FIELD_TYPE
|
||||||
local FIELD_META = constants.FIELD_META
|
local FIELD_META = constants.FIELD_META
|
||||||
|
|
||||||
---@alias canola.IconProvider fun(type: string, name: string, conf: table?, ft: string?): (icon: string, hl: string)
|
---@alias oil.IconProvider fun(type: string, name: string, conf: table?, ft: string?): (icon: string, hl: string)
|
||||||
|
|
||||||
---@param url string
|
---@param url string
|
||||||
---@return nil|string
|
---@return nil|string
|
||||||
---@return nil|string
|
---@return nil|string
|
||||||
M.parse_url = function(url)
|
M.parse_url = function(url)
|
||||||
return url:match('^(.*://)(.*)$')
|
return url:match("^(.*://)(.*)$")
|
||||||
end
|
end
|
||||||
|
|
||||||
---Escapes a filename for use in :edit
|
---Escapes a filename for use in :edit
|
||||||
|
|
@ -26,64 +26,64 @@ M.escape_filename = function(filename)
|
||||||
end
|
end
|
||||||
|
|
||||||
local _url_escape_to_char = {
|
local _url_escape_to_char = {
|
||||||
['20'] = ' ',
|
["20"] = " ",
|
||||||
['22'] = '“',
|
["22"] = "“",
|
||||||
['23'] = '#',
|
["23"] = "#",
|
||||||
['24'] = '$',
|
["24"] = "$",
|
||||||
['25'] = '%',
|
["25"] = "%",
|
||||||
['26'] = '&',
|
["26"] = "&",
|
||||||
['27'] = '‘',
|
["27"] = "‘",
|
||||||
['2B'] = '+',
|
["2B"] = "+",
|
||||||
['2C'] = ',',
|
["2C"] = ",",
|
||||||
['2F'] = '/',
|
["2F"] = "/",
|
||||||
['3A'] = ':',
|
["3A"] = ":",
|
||||||
['3B'] = ';',
|
["3B"] = ";",
|
||||||
['3C'] = '<',
|
["3C"] = "<",
|
||||||
['3D'] = '=',
|
["3D"] = "=",
|
||||||
['3E'] = '>',
|
["3E"] = ">",
|
||||||
['3F'] = '?',
|
["3F"] = "?",
|
||||||
['40'] = '@',
|
["40"] = "@",
|
||||||
['5B'] = '[',
|
["5B"] = "[",
|
||||||
['5C'] = '\\',
|
["5C"] = "\\",
|
||||||
['5D'] = ']',
|
["5D"] = "]",
|
||||||
['5E'] = '^',
|
["5E"] = "^",
|
||||||
['60'] = '`',
|
["60"] = "`",
|
||||||
['7B'] = '{',
|
["7B"] = "{",
|
||||||
['7C'] = '|',
|
["7C"] = "|",
|
||||||
['7D'] = '}',
|
["7D"] = "}",
|
||||||
['7E'] = '~',
|
["7E"] = "~",
|
||||||
}
|
}
|
||||||
local _char_to_url_escape = {}
|
local _char_to_url_escape = {}
|
||||||
for k, v in pairs(_url_escape_to_char) do
|
for k, v in pairs(_url_escape_to_char) do
|
||||||
_char_to_url_escape[v] = '%' .. k
|
_char_to_url_escape[v] = "%" .. k
|
||||||
end
|
end
|
||||||
-- TODO this uri escape handling is very incomplete
|
-- 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('.', _char_to_url_escape))
|
return (string:gsub(".", _char_to_url_escape))
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param string string
|
---@param string string
|
||||||
---@return string
|
---@return string
|
||||||
M.url_unescape = function(string)
|
M.url_unescape = function(string)
|
||||||
return (
|
return (
|
||||||
string:gsub('%%([0-9A-Fa-f][0-9A-Fa-f])', function(seq)
|
string:gsub("%%([0-9A-Fa-f][0-9A-Fa-f])", function(seq)
|
||||||
return _url_escape_to_char[seq:upper()] or ('%' .. seq)
|
return _url_escape_to_char[seq:upper()] or ("%" .. seq)
|
||||||
end)
|
end)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
---@param silent? boolean
|
---@param silent? boolean
|
||||||
---@return nil|canola.Adapter
|
---@return nil|oil.Adapter
|
||||||
M.get_adapter = function(bufnr, silent)
|
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 and not silent then
|
if not adapter and not silent then
|
||||||
vim.notify_once(
|
vim.notify_once(
|
||||||
string.format("[canola] 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
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
@ -92,7 +92,7 @@ end
|
||||||
|
|
||||||
---@param text string
|
---@param text string
|
||||||
---@param width integer|nil
|
---@param width integer|nil
|
||||||
---@param align canola.ColumnAlign
|
---@param align oil.ColumnAlign
|
||||||
---@return string padded_text
|
---@return string padded_text
|
||||||
---@return integer left_padding
|
---@return integer left_padding
|
||||||
M.pad_align = function(text, width, align)
|
M.pad_align = function(text, width, align)
|
||||||
|
|
@ -105,14 +105,14 @@ M.pad_align = function(text, width, align)
|
||||||
return text, 0
|
return text, 0
|
||||||
end
|
end
|
||||||
|
|
||||||
if align == 'right' then
|
if align == "right" then
|
||||||
return string.rep(' ', total_pad) .. text, total_pad
|
return string.rep(" ", total_pad) .. text, total_pad
|
||||||
elseif align == 'center' then
|
elseif align == "center" then
|
||||||
local left_pad = math.floor(total_pad / 2)
|
local left_pad = math.floor(total_pad / 2)
|
||||||
local right_pad = total_pad - left_pad
|
local right_pad = total_pad - left_pad
|
||||||
return string.rep(' ', left_pad) .. text .. string.rep(' ', right_pad), left_pad
|
return string.rep(" ", left_pad) .. text .. string.rep(" ", right_pad), left_pad
|
||||||
else
|
else
|
||||||
return text .. string.rep(' ', total_pad), 0
|
return text .. string.rep(" ", total_pad), 0
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -135,8 +135,8 @@ M.tbl_slice = function(tbl, start_idx, end_idx)
|
||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param entry canola.InternalEntry
|
---@param entry oil.InternalEntry
|
||||||
---@return canola.Entry
|
---@return oil.Entry
|
||||||
M.export_entry = function(entry)
|
M.export_entry = function(entry)
|
||||||
return {
|
return {
|
||||||
name = entry[FIELD_NAME],
|
name = entry[FIELD_NAME],
|
||||||
|
|
@ -150,7 +150,7 @@ end
|
||||||
---@param dest_buf_name string
|
---@param dest_buf_name string
|
||||||
---@return boolean True if the buffer was replaced instead of renamed
|
---@return boolean True if the buffer was replaced instead of renamed
|
||||||
M.rename_buffer = function(src_bufnr, dest_buf_name)
|
M.rename_buffer = function(src_bufnr, dest_buf_name)
|
||||||
if type(src_bufnr) == 'string' then
|
if type(src_bufnr) == "string" then
|
||||||
src_bufnr = vim.fn.bufadd(src_bufnr)
|
src_bufnr = vim.fn.bufadd(src_bufnr)
|
||||||
if not vim.api.nvim_buf_is_loaded(src_bufnr) then
|
if not vim.api.nvim_buf_is_loaded(src_bufnr) then
|
||||||
vim.api.nvim_buf_delete(src_bufnr, {})
|
vim.api.nvim_buf_delete(src_bufnr, {})
|
||||||
|
|
@ -164,7 +164,7 @@ M.rename_buffer = function(src_bufnr, dest_buf_name)
|
||||||
-- think that the new buffer conflicts with the file next time it tries to save.
|
-- think that the new buffer conflicts with the file next time it tries to save.
|
||||||
if not vim.loop.fs_stat(dest_buf_name) then
|
if not vim.loop.fs_stat(dest_buf_name) then
|
||||||
---@diagnostic disable-next-line: param-type-mismatch
|
---@diagnostic disable-next-line: param-type-mismatch
|
||||||
local altbuf = vim.fn.bufnr('#')
|
local altbuf = vim.fn.bufnr("#")
|
||||||
-- 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
|
||||||
|
|
@ -173,7 +173,7 @@ M.rename_buffer = function(src_bufnr, dest_buf_name)
|
||||||
-- where Neovim doesn't allow buffer modifications.
|
-- where Neovim doesn't allow buffer modifications.
|
||||||
pcall(vim.api.nvim_buf_delete, vim.fn.bufadd(bufname), {})
|
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
|
||||||
|
|
||||||
return false
|
return false
|
||||||
|
|
@ -229,7 +229,6 @@ end
|
||||||
M.cb_collect = function(count, cb)
|
M.cb_collect = function(count, cb)
|
||||||
return function(err)
|
return function(err)
|
||||||
if err then
|
if err then
|
||||||
-- selene: allow(mismatched_arg_count)
|
|
||||||
cb(err)
|
cb(err)
|
||||||
cb = function() end
|
cb = function() end
|
||||||
else
|
else
|
||||||
|
|
@ -244,22 +243,22 @@ end
|
||||||
---@param url string
|
---@param url string
|
||||||
---@return string[]
|
---@return string[]
|
||||||
local function get_possible_buffer_names_from_url(url)
|
local function get_possible_buffer_names_from_url(url)
|
||||||
local fs = require('canola.fs')
|
local fs = require("oil.fs")
|
||||||
local scheme, path = M.parse_url(url)
|
local scheme, path = M.parse_url(url)
|
||||||
if config.adapters[scheme] == 'files' then
|
if config.adapters[scheme] == "files" then
|
||||||
assert(path)
|
assert(path)
|
||||||
return { fs.posix_to_os_path(path) }
|
return { fs.posix_to_os_path(path) }
|
||||||
end
|
end
|
||||||
return { url }
|
return { url }
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param entry_type canola.EntryType
|
---@param entry_type oil.EntryType
|
||||||
---@param src_url string
|
---@param src_url string
|
||||||
---@param dest_url string
|
---@param dest_url string
|
||||||
M.update_moved_buffers = function(entry_type, src_url, dest_url)
|
M.update_moved_buffers = function(entry_type, src_url, dest_url)
|
||||||
local src_buf_names = get_possible_buffer_names_from_url(src_url)
|
local src_buf_names = get_possible_buffer_names_from_url(src_url)
|
||||||
local dest_buf_name = get_possible_buffer_names_from_url(dest_url)[1]
|
local dest_buf_name = get_possible_buffer_names_from_url(dest_url)[1]
|
||||||
if entry_type ~= 'directory' then
|
if entry_type ~= "directory" then
|
||||||
for _, src_buf_name in ipairs(src_buf_names) do
|
for _, src_buf_name in ipairs(src_buf_names) do
|
||||||
M.rename_buffer(src_buf_name, dest_buf_name)
|
M.rename_buffer(src_buf_name, dest_buf_name)
|
||||||
end
|
end
|
||||||
|
|
@ -271,15 +270,15 @@ M.update_moved_buffers = function(entry_type, src_url, dest_url)
|
||||||
for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do
|
for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do
|
||||||
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
||||||
if vim.startswith(bufname, src_url) then
|
if vim.startswith(bufname, src_url) then
|
||||||
-- Handle canola directory buffers
|
-- Handle oil directory buffers
|
||||||
vim.api.nvim_buf_set_name(bufnr, dest_url .. bufname:sub(src_url:len() + 1))
|
vim.api.nvim_buf_set_name(bufnr, dest_url .. bufname:sub(src_url:len() + 1))
|
||||||
elseif bufname ~= '' and vim.bo[bufnr].buftype == '' then
|
elseif bufname ~= "" and vim.bo[bufnr].buftype == "" then
|
||||||
-- Handle regular buffers
|
-- Handle regular buffers
|
||||||
local scheme = M.parse_url(bufname)
|
local scheme = M.parse_url(bufname)
|
||||||
|
|
||||||
-- If the buffer is a local file, make sure we're using the absolute path
|
-- If the buffer is a local file, make sure we're using the absolute path
|
||||||
if not scheme then
|
if not scheme then
|
||||||
bufname = vim.fn.fnamemodify(bufname, ':p')
|
bufname = vim.fn.fnamemodify(bufname, ":p")
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, src_buf_name in ipairs(src_buf_names) do
|
for _, src_buf_name in ipairs(src_buf_names) do
|
||||||
|
|
@ -297,23 +296,23 @@ end
|
||||||
---@return string
|
---@return string
|
||||||
---@return table|nil
|
---@return table|nil
|
||||||
M.split_config = function(name_or_config)
|
M.split_config = function(name_or_config)
|
||||||
if type(name_or_config) == 'string' then
|
if type(name_or_config) == "string" then
|
||||||
return name_or_config, nil
|
return name_or_config, nil
|
||||||
else
|
else
|
||||||
if not name_or_config[1] and name_or_config['1'] then
|
if not name_or_config[1] and name_or_config["1"] then
|
||||||
-- This was likely loaded from json, so the first element got coerced to a string key
|
-- This was likely loaded from json, so the first element got coerced to a string key
|
||||||
name_or_config[1] = name_or_config['1']
|
name_or_config[1] = name_or_config["1"]
|
||||||
name_or_config['1'] = nil
|
name_or_config["1"] = nil
|
||||||
end
|
end
|
||||||
return name_or_config[1], name_or_config
|
return name_or_config[1], name_or_config
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@alias canola.ColumnAlign "left"|"center"|"right"
|
---@alias oil.ColumnAlign "left"|"center"|"right"
|
||||||
|
|
||||||
---@param lines canola.TextChunk[][]
|
---@param lines oil.TextChunk[][]
|
||||||
---@param col_width integer[]
|
---@param col_width integer[]
|
||||||
---@param col_align? canola.ColumnAlign[]
|
---@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, col_align)
|
M.render_table = function(lines, col_width, col_align)
|
||||||
|
|
@ -325,7 +324,7 @@ M.render_table = function(lines, col_width, col_align)
|
||||||
local pieces = {}
|
local pieces = {}
|
||||||
for i, chunk in ipairs(cols) do
|
for i, chunk in ipairs(cols) do
|
||||||
local text, hl
|
local text, hl
|
||||||
if type(chunk) == 'table' then
|
if type(chunk) == "table" then
|
||||||
text = chunk[1]
|
text = chunk[1]
|
||||||
hl = chunk[2]
|
hl = chunk[2]
|
||||||
else
|
else
|
||||||
|
|
@ -334,11 +333,11 @@ M.render_table = function(lines, col_width, col_align)
|
||||||
|
|
||||||
local unpadded_len = text:len()
|
local unpadded_len = text:len()
|
||||||
local padding
|
local padding
|
||||||
text, padding = M.pad_align(text, col_width[i], col_align[i] or 'left')
|
text, padding = M.pad_align(text, col_width[i], col_align[i] or "left")
|
||||||
|
|
||||||
table.insert(pieces, text)
|
table.insert(pieces, text)
|
||||||
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 }[]
|
||||||
-- Notice that col_start and col_end are relative position inside
|
-- Notice that col_start and col_end are relative position inside
|
||||||
-- that col, so we need to add the offset to them
|
-- that col, so we need to add the offset to them
|
||||||
|
|
@ -356,7 +355,7 @@ M.render_table = function(lines, col_width, col_align)
|
||||||
end
|
end
|
||||||
col = col + text:len() + 1
|
col = col + text:len() + 1
|
||||||
end
|
end
|
||||||
table.insert(str_lines, table.concat(pieces, ' '))
|
table.insert(str_lines, table.concat(pieces, " "))
|
||||||
end
|
end
|
||||||
return str_lines, highlights
|
return str_lines, highlights
|
||||||
end
|
end
|
||||||
|
|
@ -364,7 +363,7 @@ end
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
---@param highlights any[][] List of highlights {group, lnum, col_start, col_end}
|
---@param highlights any[][] List of highlights {group, lnum, col_start, col_end}
|
||||||
M.set_highlights = function(bufnr, highlights)
|
M.set_highlights = function(bufnr, highlights)
|
||||||
local ns = vim.api.nvim_create_namespace('Canola')
|
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
|
||||||
local group, line, col_start, col_end = unpack(hl)
|
local group, line, col_start, col_end = unpack(hl)
|
||||||
|
|
@ -380,12 +379,12 @@ end
|
||||||
---@param os_slash? boolean use os filesystem slash instead of posix slash
|
---@param os_slash? boolean use os filesystem slash instead of posix slash
|
||||||
---@return string
|
---@return string
|
||||||
M.addslash = function(path, os_slash)
|
M.addslash = function(path, os_slash)
|
||||||
local slash = '/'
|
local slash = "/"
|
||||||
if os_slash and require('canola.fs').is_windows then
|
if os_slash and require("oil.fs").is_windows then
|
||||||
slash = '\\'
|
slash = "\\"
|
||||||
end
|
end
|
||||||
|
|
||||||
local endslash = path:match(slash .. '$')
|
local endslash = path:match(slash .. "$")
|
||||||
if not endslash then
|
if not endslash then
|
||||||
return path .. slash
|
return path .. slash
|
||||||
else
|
else
|
||||||
|
|
@ -396,7 +395,7 @@ end
|
||||||
---@param winid nil|integer
|
---@param winid nil|integer
|
||||||
---@return boolean
|
---@return boolean
|
||||||
M.is_floating_win = function(winid)
|
M.is_floating_win = function(winid)
|
||||||
return vim.api.nvim_win_get_config(winid or 0).relative ~= ''
|
return vim.api.nvim_win_get_config(winid or 0).relative ~= ""
|
||||||
end
|
end
|
||||||
|
|
||||||
---Recalculate the window title for the current buffer
|
---Recalculate the window title for the current buffer
|
||||||
|
|
@ -411,10 +410,10 @@ M.get_title = function(winid)
|
||||||
local title = vim.api.nvim_buf_get_name(src_buf)
|
local title = vim.api.nvim_buf_get_name(src_buf)
|
||||||
local scheme, path = M.parse_url(title)
|
local scheme, path = M.parse_url(title)
|
||||||
|
|
||||||
if config.adapters[scheme] == 'files' then
|
if config.adapters[scheme] == "files" then
|
||||||
assert(path)
|
assert(path)
|
||||||
local fs = require('canola.fs')
|
local fs = require("oil.fs")
|
||||||
title = vim.fn.fnamemodify(fs.posix_to_os_path(path), ':~')
|
title = vim.fn.fnamemodify(fs.posix_to_os_path(path), ":~")
|
||||||
end
|
end
|
||||||
return title
|
return title
|
||||||
end
|
end
|
||||||
|
|
@ -422,7 +421,7 @@ end
|
||||||
local winid_map = {}
|
local winid_map = {}
|
||||||
M.add_title_to_win = function(winid, opts)
|
M.add_title_to_win = function(winid, opts)
|
||||||
opts = opts or {}
|
opts = opts or {}
|
||||||
opts.align = opts.align or 'left'
|
opts.align = opts.align or "left"
|
||||||
if not vim.api.nvim_win_is_valid(winid) then
|
if not vim.api.nvim_win_is_valid(winid) then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
@ -439,18 +438,18 @@ M.add_title_to_win = function(winid, opts)
|
||||||
else
|
else
|
||||||
bufnr = vim.api.nvim_create_buf(false, true)
|
bufnr = vim.api.nvim_create_buf(false, true)
|
||||||
local col = 1
|
local col = 1
|
||||||
if opts.align == 'center' then
|
if opts.align == "center" then
|
||||||
col = math.floor((vim.api.nvim_win_get_width(winid) - width) / 2)
|
col = math.floor((vim.api.nvim_win_get_width(winid) - width) / 2)
|
||||||
elseif opts.align == 'right' then
|
elseif opts.align == "right" then
|
||||||
col = vim.api.nvim_win_get_width(winid) - 1 - width
|
col = vim.api.nvim_win_get_width(winid) - 1 - width
|
||||||
elseif opts.align ~= 'left' then
|
elseif opts.align ~= "left" then
|
||||||
vim.notify(
|
vim.notify(
|
||||||
string.format("Unknown canola window title alignment: '%s'", opts.align),
|
string.format("Unknown oil window title alignment: '%s'", opts.align),
|
||||||
vim.log.levels.ERROR
|
vim.log.levels.ERROR
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
title_winid = vim.api.nvim_open_win(bufnr, false, {
|
title_winid = vim.api.nvim_open_win(bufnr, false, {
|
||||||
relative = 'win',
|
relative = "win",
|
||||||
win = winid,
|
win = winid,
|
||||||
width = width,
|
width = width,
|
||||||
height = 1,
|
height = 1,
|
||||||
|
|
@ -458,20 +457,20 @@ M.add_title_to_win = function(winid, opts)
|
||||||
col = col,
|
col = col,
|
||||||
focusable = false,
|
focusable = false,
|
||||||
zindex = 151,
|
zindex = 151,
|
||||||
style = 'minimal',
|
style = "minimal",
|
||||||
noautocmd = true,
|
noautocmd = true,
|
||||||
})
|
})
|
||||||
winid_map[winid] = title_winid
|
winid_map[winid] = title_winid
|
||||||
vim.api.nvim_set_option_value(
|
vim.api.nvim_set_option_value(
|
||||||
'winblend',
|
"winblend",
|
||||||
vim.wo[winid].winblend,
|
vim.wo[winid].winblend,
|
||||||
{ scope = 'local', win = title_winid }
|
{ scope = "local", win = title_winid }
|
||||||
)
|
)
|
||||||
vim.bo[bufnr].bufhidden = 'wipe'
|
vim.bo[bufnr].bufhidden = "wipe"
|
||||||
|
|
||||||
local update_autocmd = vim.api.nvim_create_autocmd('BufWinEnter', {
|
local update_autocmd = vim.api.nvim_create_autocmd("BufWinEnter", {
|
||||||
desc = 'Update canola floating window title when buffer changes',
|
desc = "Update oil floating window title when buffer changes",
|
||||||
pattern = '*',
|
pattern = "*",
|
||||||
callback = function(params)
|
callback = function(params)
|
||||||
local winbuf = params.buf
|
local winbuf = params.buf
|
||||||
if vim.api.nvim_win_get_buf(winid) ~= winbuf then
|
if vim.api.nvim_win_get_buf(winid) ~= winbuf then
|
||||||
|
|
@ -480,17 +479,17 @@ M.add_title_to_win = function(winid, opts)
|
||||||
local new_title = M.get_title(winid)
|
local new_title = M.get_title(winid)
|
||||||
local new_width =
|
local new_width =
|
||||||
math.min(vim.api.nvim_win_get_width(winid) - 4, 2 + vim.api.nvim_strwidth(new_title))
|
math.min(vim.api.nvim_win_get_width(winid) - 4, 2 + vim.api.nvim_strwidth(new_title))
|
||||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, { ' ' .. new_title .. ' ' })
|
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, { " " .. new_title .. " " })
|
||||||
vim.bo[bufnr].modified = false
|
vim.bo[bufnr].modified = false
|
||||||
vim.api.nvim_win_set_width(title_winid, new_width)
|
vim.api.nvim_win_set_width(title_winid, new_width)
|
||||||
local new_col = 1
|
local new_col = 1
|
||||||
if opts.align == 'center' then
|
if opts.align == "center" then
|
||||||
new_col = math.floor((vim.api.nvim_win_get_width(winid) - new_width) / 2)
|
new_col = math.floor((vim.api.nvim_win_get_width(winid) - new_width) / 2)
|
||||||
elseif opts.align == 'right' then
|
elseif opts.align == "right" then
|
||||||
new_col = vim.api.nvim_win_get_width(winid) - 1 - new_width
|
new_col = vim.api.nvim_win_get_width(winid) - 1 - new_width
|
||||||
end
|
end
|
||||||
vim.api.nvim_win_set_config(title_winid, {
|
vim.api.nvim_win_set_config(title_winid, {
|
||||||
relative = 'win',
|
relative = "win",
|
||||||
win = winid,
|
win = winid,
|
||||||
row = -1,
|
row = -1,
|
||||||
col = new_col,
|
col = new_col,
|
||||||
|
|
@ -499,8 +498,8 @@ M.add_title_to_win = function(winid, opts)
|
||||||
})
|
})
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
vim.api.nvim_create_autocmd('WinClosed', {
|
vim.api.nvim_create_autocmd("WinClosed", {
|
||||||
desc = 'Close canola floating window title when floating window closes',
|
desc = "Close oil floating window title when floating window closes",
|
||||||
pattern = tostring(winid),
|
pattern = tostring(winid),
|
||||||
callback = function()
|
callback = function()
|
||||||
if title_winid and vim.api.nvim_win_is_valid(title_winid) then
|
if title_winid and vim.api.nvim_win_is_valid(title_winid) then
|
||||||
|
|
@ -513,18 +512,18 @@ M.add_title_to_win = function(winid, opts)
|
||||||
nested = true,
|
nested = true,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, { ' ' .. title .. ' ' })
|
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, { " " .. title .. " " })
|
||||||
vim.bo[bufnr].modified = false
|
vim.bo[bufnr].modified = false
|
||||||
vim.api.nvim_set_option_value(
|
vim.api.nvim_set_option_value(
|
||||||
'winhighlight',
|
"winhighlight",
|
||||||
'Normal:FloatTitle,NormalFloat:FloatTitle',
|
"Normal:FloatTitle,NormalFloat:FloatTitle",
|
||||||
{ scope = 'local', win = title_winid }
|
{ scope = "local", win = title_winid }
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param action canola.Action
|
---@param action oil.Action
|
||||||
---@return canola.Adapter
|
---@return oil.Adapter
|
||||||
---@return nil|canola.CrossAdapterAction
|
---@return nil|oil.CrossAdapterAction
|
||||||
M.get_adapter_for_action = function(action)
|
M.get_adapter_for_action = function(action)
|
||||||
local adapter = assert(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 action.dest_url then
|
if action.dest_url then
|
||||||
|
|
@ -543,7 +542,7 @@ M.get_adapter_for_action = function(action)
|
||||||
else
|
else
|
||||||
error(
|
error(
|
||||||
string.format(
|
string.format(
|
||||||
'Cannot copy files from %s -> %s; no cross-adapter transfer method found',
|
"Cannot copy files from %s -> %s; no cross-adapter transfer method found",
|
||||||
action.src_url,
|
action.src_url,
|
||||||
action.dest_url
|
action.dest_url
|
||||||
)
|
)
|
||||||
|
|
@ -560,12 +559,12 @@ end
|
||||||
---@return string
|
---@return string
|
||||||
---@return integer
|
---@return integer
|
||||||
M.h_align = function(str, align, width)
|
M.h_align = function(str, align, width)
|
||||||
if align == 'center' then
|
if align == "center" then
|
||||||
local padding = math.floor((width - vim.api.nvim_strwidth(str)) / 2)
|
local padding = math.floor((width - vim.api.nvim_strwidth(str)) / 2)
|
||||||
return string.rep(' ', padding) .. str, padding
|
return string.rep(" ", padding) .. str, padding
|
||||||
elseif align == 'right' then
|
elseif align == "right" then
|
||||||
local padding = width - vim.api.nvim_strwidth(str)
|
local padding = width - vim.api.nvim_strwidth(str)
|
||||||
return string.rep(' ', padding) .. str, padding
|
return string.rep(" ", padding) .. str, padding
|
||||||
else
|
else
|
||||||
return str, 0
|
return str, 0
|
||||||
end
|
end
|
||||||
|
|
@ -579,15 +578,15 @@ end
|
||||||
--- actions nil|string[]
|
--- actions nil|string[]
|
||||||
--- winid nil|integer
|
--- winid nil|integer
|
||||||
M.render_text = function(bufnr, text, opts)
|
M.render_text = function(bufnr, text, opts)
|
||||||
opts = vim.tbl_deep_extend('keep', opts or {}, {
|
opts = vim.tbl_deep_extend("keep", opts or {}, {
|
||||||
h_align = 'center',
|
h_align = "center",
|
||||||
v_align = 'center',
|
v_align = "center",
|
||||||
})
|
})
|
||||||
---@cast opts -nil
|
---@cast opts -nil
|
||||||
if not vim.api.nvim_buf_is_valid(bufnr) then
|
if not vim.api.nvim_buf_is_valid(bufnr) then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
if type(text) == 'string' then
|
if type(text) == "string" then
|
||||||
text = { text }
|
text = { text }
|
||||||
end
|
end
|
||||||
local height = 40
|
local height = 40
|
||||||
|
|
@ -609,17 +608,17 @@ M.render_text = function(bufnr, text, opts)
|
||||||
local lines = {}
|
local lines = {}
|
||||||
|
|
||||||
-- Add vertical spacing for vertical alignment
|
-- Add vertical spacing for vertical alignment
|
||||||
if opts.v_align == 'center' then
|
if opts.v_align == "center" then
|
||||||
for _ = 1, (height / 2) - (#text / 2) do
|
for _ = 1, (height / 2) - (#text / 2) do
|
||||||
table.insert(lines, '')
|
table.insert(lines, "")
|
||||||
end
|
end
|
||||||
elseif opts.v_align == 'bottom' then
|
elseif opts.v_align == "bottom" then
|
||||||
local num_lines = height
|
local num_lines = height
|
||||||
if opts.actions then
|
if opts.actions then
|
||||||
num_lines = num_lines - 2
|
num_lines = num_lines - 2
|
||||||
end
|
end
|
||||||
while #lines + #text < num_lines do
|
while #lines + #text < num_lines do
|
||||||
table.insert(lines, '')
|
table.insert(lines, "")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -633,12 +632,12 @@ M.render_text = function(bufnr, text, opts)
|
||||||
local highlights = {}
|
local highlights = {}
|
||||||
if opts.actions then
|
if opts.actions then
|
||||||
while #lines < height - 1 do
|
while #lines < height - 1 do
|
||||||
table.insert(lines, '')
|
table.insert(lines, "")
|
||||||
end
|
end
|
||||||
local last_line, padding = M.h_align(table.concat(opts.actions, ' '), 'center', width)
|
local last_line, padding = M.h_align(table.concat(opts.actions, " "), "center", width)
|
||||||
local col = padding
|
local col = padding
|
||||||
for _, action in ipairs(opts.actions) do
|
for _, action in ipairs(opts.actions) do
|
||||||
table.insert(highlights, { 'Special', #lines, col, col + 3 })
|
table.insert(highlights, { "Special", #lines, col, col + 3 })
|
||||||
col = padding + action:len() + 4
|
col = padding + action:len() + 4
|
||||||
end
|
end
|
||||||
table.insert(lines, last_line)
|
table.insert(lines, last_line)
|
||||||
|
|
@ -657,10 +656,10 @@ end
|
||||||
M.run_in_fullscreen_win = function(bufnr, callback)
|
M.run_in_fullscreen_win = function(bufnr, callback)
|
||||||
if not bufnr then
|
if not bufnr then
|
||||||
bufnr = vim.api.nvim_create_buf(false, true)
|
bufnr = vim.api.nvim_create_buf(false, true)
|
||||||
vim.bo[bufnr].bufhidden = 'wipe'
|
vim.bo[bufnr].bufhidden = "wipe"
|
||||||
end
|
end
|
||||||
local winid = vim.api.nvim_open_win(bufnr, false, {
|
local winid = vim.api.nvim_open_win(bufnr, false, {
|
||||||
relative = 'editor',
|
relative = "editor",
|
||||||
width = vim.o.columns,
|
width = vim.o.columns,
|
||||||
height = vim.o.lines,
|
height = vim.o.lines,
|
||||||
row = 0,
|
row = 0,
|
||||||
|
|
@ -668,19 +667,19 @@ M.run_in_fullscreen_win = function(bufnr, callback)
|
||||||
noautocmd = true,
|
noautocmd = true,
|
||||||
})
|
})
|
||||||
local winnr = vim.api.nvim_win_get_number(winid)
|
local winnr = vim.api.nvim_win_get_number(winid)
|
||||||
vim.cmd.wincmd({ count = winnr, args = { 'w' }, mods = { noautocmd = true } })
|
vim.cmd.wincmd({ count = winnr, args = { "w" }, mods = { noautocmd = true } })
|
||||||
callback()
|
callback()
|
||||||
vim.cmd.close({ count = winnr, mods = { noautocmd = true, emsg_silent = true } })
|
vim.cmd.close({ count = winnr, mods = { noautocmd = true, emsg_silent = true } })
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
---@return boolean
|
---@return boolean
|
||||||
M.is_canola_bufnr = function(bufnr)
|
M.is_oil_bufnr = function(bufnr)
|
||||||
local filetype = vim.bo[bufnr].filetype
|
local filetype = vim.bo[bufnr].filetype
|
||||||
if filetype == 'canola' then
|
if filetype == "oil" then
|
||||||
return true
|
return true
|
||||||
elseif filetype ~= '' then
|
elseif filetype ~= "" then
|
||||||
-- If the filetype is set and is NOT "canola", then it's not an canola buffer
|
-- If the filetype is set and is NOT "oil", then it's not an oil buffer
|
||||||
return false
|
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))
|
||||||
|
|
@ -694,10 +693,10 @@ M.hack_around_termopen_autocmd = function(prev_mode)
|
||||||
vim.defer_fn(function()
|
vim.defer_fn(function()
|
||||||
local new_mode = vim.api.nvim_get_mode().mode
|
local new_mode = vim.api.nvim_get_mode().mode
|
||||||
if new_mode ~= prev_mode then
|
if new_mode ~= prev_mode then
|
||||||
if string.find(new_mode, 'i') == 1 then
|
if string.find(new_mode, "i") == 1 then
|
||||||
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes('<ESC>', true, true, true), 'n', false)
|
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("<ESC>", true, true, true), "n", false)
|
||||||
if string.find(prev_mode, 'v') == 1 or string.find(prev_mode, 'V') == 1 then
|
if string.find(prev_mode, "v") == 1 or string.find(prev_mode, "V") == 1 then
|
||||||
vim.cmd.normal({ bang = true, args = { 'gv' } })
|
vim.cmd.normal({ bang = true, args = { "gv" } })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -713,7 +712,7 @@ M.get_preview_win = function(opts)
|
||||||
if
|
if
|
||||||
vim.api.nvim_win_is_valid(winid)
|
vim.api.nvim_win_is_valid(winid)
|
||||||
and vim.wo[winid].previewwindow
|
and vim.wo[winid].previewwindow
|
||||||
and (opts.include_not_owned or vim.w[winid]['canola_preview'])
|
and (opts.include_not_owned or vim.w[winid]["oil_preview"])
|
||||||
then
|
then
|
||||||
return winid
|
return winid
|
||||||
end
|
end
|
||||||
|
|
@ -722,13 +721,13 @@ end
|
||||||
|
|
||||||
---@return fun() restore Function that restores the cursor
|
---@return fun() restore Function that restores the cursor
|
||||||
M.hide_cursor = function()
|
M.hide_cursor = function()
|
||||||
vim.api.nvim_set_hl(0, 'CanolaPreviewCursor', { nocombine = true, blend = 100 })
|
vim.api.nvim_set_hl(0, "OilPreviewCursor", { nocombine = true, blend = 100 })
|
||||||
local original_guicursor = vim.go.guicursor
|
local original_guicursor = vim.go.guicursor
|
||||||
vim.go.guicursor = 'a:CanolaPreviewCursor/CanolaPreviewCursor'
|
vim.go.guicursor = "a:OilPreviewCursor/OilPreviewCursor"
|
||||||
|
|
||||||
return function()
|
return function()
|
||||||
-- HACK: see https://github.com/neovim/neovim/issues/21018
|
-- HACK: see https://github.com/neovim/neovim/issues/21018
|
||||||
vim.go.guicursor = 'a:'
|
vim.go.guicursor = "a:"
|
||||||
vim.cmd.redrawstatus()
|
vim.cmd.redrawstatus()
|
||||||
vim.go.guicursor = original_guicursor
|
vim.go.guicursor = original_guicursor
|
||||||
end
|
end
|
||||||
|
|
@ -758,12 +757,12 @@ M.buf_get_win = function(bufnr, preferred_win)
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param adapter canola.Adapter
|
---@param adapter oil.Adapter
|
||||||
---@param url string
|
---@param url string
|
||||||
---@param opts {columns?: string[], no_cache?: boolean}
|
---@param opts {columns?: string[], no_cache?: boolean}
|
||||||
---@param callback fun(err: nil|string, entries: nil|canola.InternalEntry[])
|
---@param callback fun(err: nil|string, entries: nil|oil.InternalEntry[])
|
||||||
M.adapter_list_all = function(adapter, url, opts, callback)
|
M.adapter_list_all = function(adapter, url, opts, callback)
|
||||||
local cache = require('canola.cache')
|
local cache = require("oil.cache")
|
||||||
if not opts.no_cache then
|
if not opts.no_cache then
|
||||||
local entries = cache.list_url(url)
|
local entries = cache.list_url(url)
|
||||||
if not vim.tbl_isempty(entries) then
|
if not vim.tbl_isempty(entries) then
|
||||||
|
|
@ -787,27 +786,27 @@ M.adapter_list_all = function(adapter, url, opts, callback)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
---Send files from the current canola directory to quickfix
|
---Send files from the current oil directory to quickfix
|
||||||
---based on the provided options.
|
---based on the provided options.
|
||||||
---@param opts {target?: "qflist"|"loclist", action?: "r"|"a", only_matching_search?: boolean}
|
---@param opts {target?: "qflist"|"loclist", action?: "r"|"a", only_matching_search?: boolean}
|
||||||
M.send_to_quickfix = function(opts)
|
M.send_to_quickfix = function(opts)
|
||||||
if type(opts) ~= 'table' then
|
if type(opts) ~= "table" then
|
||||||
opts = {}
|
opts = {}
|
||||||
end
|
end
|
||||||
local canola = require('canola')
|
local oil = require("oil")
|
||||||
local dir = canola.get_current_dir()
|
local dir = oil.get_current_dir()
|
||||||
if type(dir) ~= 'string' then
|
if type(dir) ~= "string" then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local range = M.get_visual_range()
|
local range = M.get_visual_range()
|
||||||
if not range then
|
if not range then
|
||||||
range = { start_lnum = 1, end_lnum = vim.fn.line('$') }
|
range = { start_lnum = 1, end_lnum = vim.fn.line("$") }
|
||||||
end
|
end
|
||||||
local match_all = not opts.only_matching_search
|
local match_all = not opts.only_matching_search
|
||||||
local qf_entries = {}
|
local qf_entries = {}
|
||||||
for i = range.start_lnum, range.end_lnum do
|
for i = range.start_lnum, range.end_lnum do
|
||||||
local entry = canola.get_entry_on_line(0, i)
|
local entry = oil.get_entry_on_line(0, i)
|
||||||
if entry and entry.type == 'file' and (match_all or M.is_matching(entry)) then
|
if entry and entry.type == "file" and (match_all or M.is_matching(entry)) then
|
||||||
local qf_entry = {
|
local qf_entry = {
|
||||||
filename = dir .. entry.name,
|
filename = dir .. entry.name,
|
||||||
lnum = 1,
|
lnum = 1,
|
||||||
|
|
@ -818,26 +817,26 @@ M.send_to_quickfix = function(opts)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if #qf_entries == 0 then
|
if #qf_entries == 0 then
|
||||||
vim.notify('[canola] No entries found to send to quickfix', vim.log.levels.WARN)
|
vim.notify("[oil] No entries found to send to quickfix", vim.log.levels.WARN)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
vim.api.nvim_exec_autocmds('QuickFixCmdPre', {})
|
vim.api.nvim_exec_autocmds("QuickFixCmdPre", {})
|
||||||
local qf_title = 'canola files'
|
local qf_title = "oil files"
|
||||||
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()
|
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()
|
vim.cmd.copen()
|
||||||
end
|
end
|
||||||
vim.api.nvim_exec_autocmds('QuickFixCmdPost', {})
|
vim.api.nvim_exec_autocmds("QuickFixCmdPost", {})
|
||||||
end
|
end
|
||||||
|
|
||||||
---@return boolean
|
---@return boolean
|
||||||
M.is_visual_mode = function()
|
M.is_visual_mode = function()
|
||||||
local mode = vim.api.nvim_get_mode().mode
|
local mode = vim.api.nvim_get_mode().mode
|
||||||
return mode:match('^[vV]') ~= nil
|
return mode:match("^[vV]") ~= nil
|
||||||
end
|
end
|
||||||
|
|
||||||
---Get the current visual selection range. If not in visual mode, return nil.
|
---Get the current visual selection range. If not in visual mode, return nil.
|
||||||
|
|
@ -848,7 +847,7 @@ M.get_visual_range = function()
|
||||||
end
|
end
|
||||||
-- This is the best way to get the visual selection at the moment
|
-- This is the best way to get the visual selection at the moment
|
||||||
-- https://github.com/neovim/neovim/pull/13896
|
-- https://github.com/neovim/neovim/pull/13896
|
||||||
local _, start_lnum, _, _ = unpack(vim.fn.getpos('v'))
|
local _, start_lnum, _, _ = unpack(vim.fn.getpos("v"))
|
||||||
local _, end_lnum, _, _, _ = unpack(vim.fn.getcurpos())
|
local _, end_lnum, _, _, _ = unpack(vim.fn.getcurpos())
|
||||||
if start_lnum > end_lnum then
|
if start_lnum > end_lnum then
|
||||||
start_lnum, end_lnum = end_lnum, start_lnum
|
start_lnum, end_lnum = end_lnum, start_lnum
|
||||||
|
|
@ -856,7 +855,7 @@ M.get_visual_range = function()
|
||||||
return { start_lnum = start_lnum, end_lnum = end_lnum }
|
return { start_lnum = start_lnum, end_lnum = end_lnum }
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param entry canola.Entry
|
---@param entry oil.Entry
|
||||||
---@return boolean
|
---@return boolean
|
||||||
M.is_matching = function(entry)
|
M.is_matching = function(entry)
|
||||||
-- if search highlightig is not enabled, all files are considered to match
|
-- if search highlightig is not enabled, all files are considered to match
|
||||||
|
|
@ -864,7 +863,7 @@ M.is_matching = function(entry)
|
||||||
if search_highlighting_is_off then
|
if search_highlighting_is_off then
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
local pattern = vim.fn.getreg('/')
|
local pattern = vim.fn.getreg("/")
|
||||||
local position_of_match = vim.fn.match(entry.name, pattern)
|
local position_of_match = vim.fn.match(entry.name, pattern)
|
||||||
return position_of_match ~= -1
|
return position_of_match ~= -1
|
||||||
end
|
end
|
||||||
|
|
@ -875,11 +874,11 @@ M.run_after_load = function(bufnr, callback)
|
||||||
if bufnr == 0 then
|
if bufnr == 0 then
|
||||||
bufnr = vim.api.nvim_get_current_buf()
|
bufnr = vim.api.nvim_get_current_buf()
|
||||||
end
|
end
|
||||||
if vim.b[bufnr].canola_ready then
|
if vim.b[bufnr].oil_ready then
|
||||||
callback()
|
callback()
|
||||||
else
|
else
|
||||||
vim.api.nvim_create_autocmd('User', {
|
vim.api.nvim_create_autocmd("User", {
|
||||||
pattern = 'CanolaEnter',
|
pattern = "OilEnter",
|
||||||
callback = function(args)
|
callback = function(args)
|
||||||
if args.data.buf == bufnr then
|
if args.data.buf == bufnr then
|
||||||
vim.api.nvim_buf_call(bufnr, callback)
|
vim.api.nvim_buf_call(bufnr, callback)
|
||||||
|
|
@ -890,25 +889,25 @@ M.run_after_load = function(bufnr, callback)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param entry canola.Entry
|
---@param entry oil.Entry
|
||||||
---@return boolean
|
---@return boolean
|
||||||
M.is_directory = function(entry)
|
M.is_directory = function(entry)
|
||||||
local is_directory = entry.type == 'directory'
|
local is_directory = entry.type == "directory"
|
||||||
or (
|
or (
|
||||||
entry.type == 'link'
|
entry.type == "link"
|
||||||
and entry.meta
|
and entry.meta
|
||||||
and entry.meta.link_stat
|
and entry.meta.link_stat
|
||||||
and entry.meta.link_stat.type == 'directory'
|
and entry.meta.link_stat.type == "directory"
|
||||||
)
|
)
|
||||||
return is_directory == true
|
return is_directory == true
|
||||||
end
|
end
|
||||||
|
|
||||||
---Get the :edit path for an entry
|
---Get the :edit path for an entry
|
||||||
---@param bufnr integer The canola buffer that contains the entry
|
---@param bufnr integer The oil buffer that contains the entry
|
||||||
---@param entry canola.Entry
|
---@param entry oil.Entry
|
||||||
---@param callback fun(normalized_url: string)
|
---@param callback fun(normalized_url: string)
|
||||||
M.get_edit_path = function(bufnr, entry, callback)
|
M.get_edit_path = function(bufnr, entry, callback)
|
||||||
local pathutil = require('canola.pathutil')
|
local pathutil = require("oil.pathutil")
|
||||||
|
|
||||||
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)
|
||||||
|
|
@ -917,10 +916,10 @@ M.get_edit_path = function(bufnr, entry, callback)
|
||||||
|
|
||||||
local url = scheme .. dir .. entry.name
|
local url = scheme .. dir .. entry.name
|
||||||
if M.is_directory(entry) then
|
if M.is_directory(entry) then
|
||||||
url = url .. '/'
|
url = url .. "/"
|
||||||
end
|
end
|
||||||
|
|
||||||
if entry.name == '..' then
|
if entry.name == ".." then
|
||||||
callback(scheme .. pathutil.parent(dir))
|
callback(scheme .. pathutil.parent(dir))
|
||||||
elseif adapter.get_entry_path then
|
elseif adapter.get_entry_path then
|
||||||
adapter.get_entry_path(url, entry, callback)
|
adapter.get_entry_path(url, entry, callback)
|
||||||
|
|
@ -930,74 +929,45 @@ M.get_edit_path = function(bufnr, entry, callback)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Check for an icon provider and return a common icon provider API
|
--- Check for an icon provider and return a common icon provider API
|
||||||
---@return (canola.IconProvider)?
|
---@return (oil.IconProvider)?
|
||||||
M.get_icon_provider = function()
|
M.get_icon_provider = function()
|
||||||
-- prefer mini.icons
|
-- prefer mini.icons
|
||||||
local _, mini_icons = pcall(require, 'mini.icons')
|
local _, mini_icons = pcall(require, "mini.icons")
|
||||||
-- selene: allow(global_usage)
|
|
||||||
---@diagnostic disable-next-line: undefined-field
|
---@diagnostic disable-next-line: undefined-field
|
||||||
if _G.MiniIcons then
|
if _G.MiniIcons then -- `_G.MiniIcons` is a better check to see if the module is setup
|
||||||
return function(type, name, conf, ft)
|
return function(type, name, conf, ft)
|
||||||
if ft then
|
if ft then
|
||||||
return mini_icons.get('filetype', ft)
|
return mini_icons.get("filetype", ft)
|
||||||
end
|
end
|
||||||
return mini_icons.get(type == 'directory' and 'directory' or 'file', name)
|
return mini_icons.get(type == "directory" and "directory" or "file", name)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local has_nonicons, nonicons = pcall(require, 'nonicons')
|
-- fallback to `nvim-web-devicons`
|
||||||
if has_nonicons and nonicons.get_icon then
|
local has_devicons, devicons = pcall(require, "nvim-web-devicons")
|
||||||
local has_devicons, devicons = pcall(require, 'nvim-web-devicons')
|
if has_devicons then
|
||||||
if not has_devicons then
|
|
||||||
devicons = nil
|
|
||||||
end
|
|
||||||
return function(type, name, conf, ft)
|
return function(type, name, conf, ft)
|
||||||
if type == 'directory' then
|
if type == "directory" then
|
||||||
local icon, hl = nonicons.get('file-directory-fill')
|
return conf and conf.directory or "", "OilDirIcon"
|
||||||
return icon or (conf and conf.directory or ''), hl or 'CanolaDirIcon'
|
else
|
||||||
end
|
if ft then
|
||||||
if ft then
|
local ft_icon, ft_hl = devicons.get_icon_by_filetype(ft)
|
||||||
local ft_icon, ft_hl = nonicons.get_icon_by_filetype(ft)
|
if ft_icon and ft_icon ~= "" then
|
||||||
if ft_icon then
|
return ft_icon, ft_hl
|
||||||
return ft_icon, ft_hl or 'CanolaFileIcon'
|
end
|
||||||
end
|
end
|
||||||
|
local icon, hl = devicons.get_icon(name)
|
||||||
|
hl = hl or "OilFileIcon"
|
||||||
|
icon = icon or (conf and conf.default_file or "")
|
||||||
|
return icon, hl
|
||||||
end
|
end
|
||||||
local icon, hl = nonicons.get_icon(name)
|
|
||||||
if icon then
|
|
||||||
return icon, hl or 'CanolaFileIcon'
|
|
||||||
end
|
|
||||||
local fallback, fallback_hl = nonicons.get('file')
|
|
||||||
return fallback or (conf and conf.default_file or ''), fallback_hl or 'CanolaFileIcon'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local has_devicons, devicons = pcall(require, 'nvim-web-devicons')
|
|
||||||
|
|
||||||
if not has_devicons then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
return function(type, name, conf, ft)
|
|
||||||
if type == 'directory' then
|
|
||||||
return conf and conf.directory or '', 'CanolaDirIcon'
|
|
||||||
else
|
|
||||||
if ft then
|
|
||||||
local ft_icon, ft_hl = devicons.get_icon_by_filetype(ft)
|
|
||||||
if ft_icon and ft_icon ~= '' then
|
|
||||||
return ft_icon, ft_hl
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local icon, hl = devicons.get_icon(name)
|
|
||||||
hl = hl or 'CanolaFileIcon'
|
|
||||||
icon = icon or (conf and conf.default_file or '')
|
|
||||||
return icon, hl
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---Read a buffer into a scratch buffer and apply syntactic highlighting when possible
|
---Read a buffer into a scratch buffer and apply syntactic highlighting when possible
|
||||||
---@param path string The path to the file to read
|
---@param path string The path to the file to read
|
||||||
---@param preview_method canola.PreviewMethod
|
---@param preview_method oil.PreviewMethod
|
||||||
---@return nil|integer
|
---@return nil|integer
|
||||||
M.read_file_to_scratch_buffer = function(path, preview_method)
|
M.read_file_to_scratch_buffer = function(path, preview_method)
|
||||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||||
|
|
@ -1005,25 +975,24 @@ M.read_file_to_scratch_buffer = function(path, preview_method)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
vim.bo[bufnr].bufhidden = 'wipe'
|
vim.bo[bufnr].bufhidden = "wipe"
|
||||||
vim.bo[bufnr].buftype = 'nofile'
|
vim.bo[bufnr].buftype = "nofile"
|
||||||
|
|
||||||
local has_lines, read_res
|
local has_lines, read_res
|
||||||
if preview_method == 'fast_scratch' then
|
if preview_method == "fast_scratch" then
|
||||||
has_lines, read_res = pcall(vim.fn.readfile, path, '', vim.o.lines)
|
has_lines, read_res = pcall(vim.fn.readfile, path, "", vim.o.lines)
|
||||||
else
|
else
|
||||||
has_lines, read_res = pcall(vim.fn.readfile, path)
|
has_lines, read_res = pcall(vim.fn.readfile, path)
|
||||||
end
|
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)
|
||||||
if not ok then
|
if not ok then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local ft = vim.filetype.match({ filename = path, buf = bufnr })
|
local ft = vim.filetype.match({ filename = path, buf = bufnr })
|
||||||
if ft and ft ~= '' and vim.treesitter.language.get_lang then
|
if ft and ft ~= "" and vim.treesitter.language.get_lang then
|
||||||
local lang = vim.treesitter.language.get_lang(ft)
|
local lang = vim.treesitter.language.get_lang(ft)
|
||||||
-- selene: allow(empty_if)
|
|
||||||
if not pcall(vim.treesitter.start, bufnr, lang) then
|
if not pcall(vim.treesitter.start, bufnr, lang) then
|
||||||
vim.bo[bufnr].syntax = ft
|
vim.bo[bufnr].syntax = ft
|
||||||
else
|
else
|
||||||
|
|
@ -1031,8 +1000,8 @@ M.read_file_to_scratch_buffer = function(path, preview_method)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Replace the scratch buffer with a real buffer if we enter it
|
-- Replace the scratch buffer with a real buffer if we enter it
|
||||||
vim.api.nvim_create_autocmd('BufEnter', {
|
vim.api.nvim_create_autocmd("BufEnter", {
|
||||||
desc = 'canola.nvim replace scratch buffer with real buffer',
|
desc = "oil.nvim replace scratch buffer with real buffer",
|
||||||
buffer = bufnr,
|
buffer = bufnr,
|
||||||
callback = function()
|
callback = function()
|
||||||
local winid = vim.api.nvim_get_current_win()
|
local winid = vim.api.nvim_get_current_win()
|
||||||
|
|
@ -1044,8 +1013,8 @@ M.read_file_to_scratch_buffer = function(path, preview_method)
|
||||||
-- If we're still in a preview window, make sure this buffer still gets treated as a
|
-- If we're still in a preview window, make sure this buffer still gets treated as a
|
||||||
-- preview
|
-- preview
|
||||||
if vim.wo.previewwindow then
|
if vim.wo.previewwindow then
|
||||||
vim.bo.bufhidden = 'wipe'
|
vim.bo.bufhidden = "wipe"
|
||||||
vim.b.canola_preview_buffer = true
|
vim.b.oil_preview_buffer = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
@ -1061,7 +1030,7 @@ local _regcache = {}
|
||||||
---@return boolean
|
---@return boolean
|
||||||
M.file_matches_bufreadcmd = function(filename)
|
M.file_matches_bufreadcmd = function(filename)
|
||||||
local autocmds = vim.api.nvim_get_autocmds({
|
local autocmds = vim.api.nvim_get_autocmds({
|
||||||
event = 'BufReadCmd',
|
event = "BufReadCmd",
|
||||||
})
|
})
|
||||||
for _, au in ipairs(autocmds) do
|
for _, au in ipairs(autocmds) do
|
||||||
local pat = _regcache[au.pattern]
|
local pat = _regcache[au.pattern]
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
local uv = vim.uv or vim.loop
|
local uv = vim.uv or vim.loop
|
||||||
local cache = require('canola.cache')
|
local cache = require("oil.cache")
|
||||||
local columns = require('canola.columns')
|
local columns = require("oil.columns")
|
||||||
local config = require('canola.config')
|
local config = require("oil.config")
|
||||||
local constants = require('canola.constants')
|
local constants = require("oil.constants")
|
||||||
local fs = require('canola.fs')
|
local fs = require("oil.fs")
|
||||||
local keymap_util = require('canola.keymap_util')
|
local keymap_util = require("oil.keymap_util")
|
||||||
local loading = require('canola.loading')
|
local loading = require("oil.loading")
|
||||||
local util = require('canola.util')
|
local util = require("oil.util")
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local FIELD_ID = constants.FIELD_ID
|
local FIELD_ID = constants.FIELD_ID
|
||||||
|
|
@ -18,7 +18,7 @@ local FIELD_META = constants.FIELD_META
|
||||||
local last_cursor_entry = {}
|
local last_cursor_entry = {}
|
||||||
|
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
---@param entry canola.InternalEntry
|
---@param entry oil.InternalEntry
|
||||||
---@return boolean display
|
---@return boolean display
|
||||||
---@return boolean is_hidden Whether the file is classified as a hidden file
|
---@return boolean is_hidden Whether the file is classified as a hidden file
|
||||||
M.should_display = function(bufnr, entry)
|
M.should_display = function(bufnr, entry)
|
||||||
|
|
@ -41,7 +41,7 @@ end
|
||||||
|
|
||||||
---Set the cursor to the last_cursor_entry if one exists
|
---Set the cursor to the last_cursor_entry if one exists
|
||||||
M.maybe_set_cursor = function()
|
M.maybe_set_cursor = function()
|
||||||
local canola = require('canola')
|
local oil = require("oil")
|
||||||
local bufname = vim.api.nvim_buf_get_name(0)
|
local bufname = vim.api.nvim_buf_get_name(0)
|
||||||
local entry_name = last_cursor_entry[bufname]
|
local entry_name = last_cursor_entry[bufname]
|
||||||
if not entry_name then
|
if not entry_name then
|
||||||
|
|
@ -49,10 +49,10 @@ M.maybe_set_cursor = function()
|
||||||
end
|
end
|
||||||
local line_count = vim.api.nvim_buf_line_count(0)
|
local line_count = vim.api.nvim_buf_line_count(0)
|
||||||
for lnum = 1, line_count do
|
for lnum = 1, line_count do
|
||||||
local entry = canola.get_entry_on_line(0, lnum)
|
local entry = oil.get_entry_on_line(0, lnum)
|
||||||
if entry and entry.name == entry_name then
|
if entry and entry.name == entry_name then
|
||||||
local line = vim.api.nvim_buf_get_lines(0, lnum - 1, lnum, true)[1]
|
local line = vim.api.nvim_buf_get_lines(0, lnum - 1, lnum, true)[1]
|
||||||
local id_str = line:match('^/(%d+)')
|
local id_str = line:match("^/(%d+)")
|
||||||
local col = line:find(entry_name, 1, true) or (id_str:len() + 1)
|
local col = line:find(entry_name, 1, true) or (id_str:len() + 1)
|
||||||
vim.api.nvim_win_set_cursor(0, { lnum, col - 1 })
|
vim.api.nvim_win_set_cursor(0, { lnum, col - 1 })
|
||||||
M.set_last_cursor(bufname, nil)
|
M.set_last_cursor(bufname, nil)
|
||||||
|
|
@ -78,14 +78,14 @@ local function are_any_modified()
|
||||||
end
|
end
|
||||||
|
|
||||||
local function is_unix_executable(entry)
|
local function is_unix_executable(entry)
|
||||||
if entry[FIELD_TYPE] == 'directory' then
|
if entry[FIELD_TYPE] == "directory" then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
if not meta or not meta.stat then
|
if not meta or not meta.stat then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
if meta.stat.type == 'directory' then
|
if meta.stat.type == "directory" then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -98,51 +98,51 @@ end
|
||||||
M.toggle_hidden = function()
|
M.toggle_hidden = function()
|
||||||
local any_modified = are_any_modified()
|
local any_modified = are_any_modified()
|
||||||
if any_modified then
|
if any_modified then
|
||||||
vim.notify('Cannot toggle hidden files when you have unsaved changes', vim.log.levels.WARN)
|
vim.notify("Cannot toggle hidden files when you have unsaved changes", vim.log.levels.WARN)
|
||||||
else
|
else
|
||||||
config.view_options.show_hidden = not config.view_options.show_hidden
|
config.view_options.show_hidden = not config.view_options.show_hidden
|
||||||
M.rerender_all_canola_buffers({ refetch = false })
|
M.rerender_all_oil_buffers({ refetch = false })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param is_hidden_file fun(filename: string, bufnr: integer, entry: canola.Entry): boolean
|
---@param is_hidden_file fun(filename: string, bufnr: integer, entry: oil.Entry): boolean
|
||||||
M.set_is_hidden_file = function(is_hidden_file)
|
M.set_is_hidden_file = function(is_hidden_file)
|
||||||
local any_modified = are_any_modified()
|
local any_modified = are_any_modified()
|
||||||
if any_modified then
|
if any_modified then
|
||||||
vim.notify('Cannot change is_hidden_file when you have unsaved changes', vim.log.levels.WARN)
|
vim.notify("Cannot change is_hidden_file when you have unsaved changes", vim.log.levels.WARN)
|
||||||
else
|
else
|
||||||
config.view_options.is_hidden_file = is_hidden_file
|
config.view_options.is_hidden_file = is_hidden_file
|
||||||
M.rerender_all_canola_buffers({ refetch = false })
|
M.rerender_all_oil_buffers({ refetch = false })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
M.set_columns = function(cols)
|
M.set_columns = function(cols)
|
||||||
local any_modified = are_any_modified()
|
local any_modified = are_any_modified()
|
||||||
if any_modified then
|
if any_modified then
|
||||||
vim.notify('Cannot change columns when you have unsaved changes', vim.log.levels.WARN)
|
vim.notify("Cannot change columns when you have unsaved changes", vim.log.levels.WARN)
|
||||||
else
|
else
|
||||||
config.columns = cols
|
config.columns = cols
|
||||||
-- TODO only refetch if we don't have all the necessary data for the columns
|
-- TODO only refetch if we don't have all the necessary data for the columns
|
||||||
M.rerender_all_canola_buffers({ refetch = true })
|
M.rerender_all_oil_buffers({ refetch = true })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
M.set_sort = function(new_sort)
|
M.set_sort = function(new_sort)
|
||||||
local any_modified = are_any_modified()
|
local any_modified = are_any_modified()
|
||||||
if any_modified then
|
if any_modified then
|
||||||
vim.notify('Cannot change sorting when you have unsaved changes', vim.log.levels.WARN)
|
vim.notify("Cannot change sorting when you have unsaved changes", vim.log.levels.WARN)
|
||||||
else
|
else
|
||||||
config.view_options.sort = new_sort
|
config.view_options.sort = new_sort
|
||||||
-- TODO only refetch if we don't have all the necessary data for the columns
|
-- TODO only refetch if we don't have all the necessary data for the columns
|
||||||
M.rerender_all_canola_buffers({ refetch = true })
|
M.rerender_all_oil_buffers({ refetch = true })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class canola.ViewData
|
---@class oil.ViewData
|
||||||
---@field fs_event? any uv_fs_event_t
|
---@field fs_event? any uv_fs_event_t
|
||||||
|
|
||||||
-- List of bufnrs
|
-- List of bufnrs
|
||||||
---@type table<integer, canola.ViewData>
|
---@type table<integer, oil.ViewData>
|
||||||
local session = {}
|
local session = {}
|
||||||
|
|
||||||
---@return integer[]
|
---@return integer[]
|
||||||
|
|
@ -151,7 +151,7 @@ M.get_all_buffers = function()
|
||||||
end
|
end
|
||||||
|
|
||||||
local buffers_locked = false
|
local buffers_locked = false
|
||||||
---Make all canola buffers nomodifiable
|
---Make all oil buffers nomodifiable
|
||||||
M.lock_buffers = function()
|
M.lock_buffers = function()
|
||||||
buffers_locked = true
|
buffers_locked = true
|
||||||
for bufnr in pairs(session) do
|
for bufnr in pairs(session) do
|
||||||
|
|
@ -161,7 +161,7 @@ M.lock_buffers = function()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---Restore normal modifiable settings for canola buffers
|
---Restore normal modifiable settings for oil buffers
|
||||||
M.unlock_buffers = function()
|
M.unlock_buffers = function()
|
||||||
buffers_locked = false
|
buffers_locked = false
|
||||||
for bufnr in pairs(session) do
|
for bufnr in pairs(session) do
|
||||||
|
|
@ -177,8 +177,8 @@ end
|
||||||
---@param opts? table
|
---@param opts? table
|
||||||
---@param callback? fun(err: nil|string)
|
---@param callback? fun(err: nil|string)
|
||||||
---@note
|
---@note
|
||||||
--- This DISCARDS ALL MODIFICATIONS a user has made to canola buffers
|
--- This DISCARDS ALL MODIFICATIONS a user has made to oil buffers
|
||||||
M.rerender_all_canola_buffers = function(opts, callback)
|
M.rerender_all_oil_buffers = function(opts, callback)
|
||||||
opts = opts or {}
|
opts = opts or {}
|
||||||
local buffers = M.get_all_buffers()
|
local buffers = M.get_all_buffers()
|
||||||
local hidden_buffers = {}
|
local hidden_buffers = {}
|
||||||
|
|
@ -193,7 +193,7 @@ M.rerender_all_canola_buffers = function(opts, callback)
|
||||||
local cb = util.cb_collect(#buffers, callback or function() end)
|
local cb = util.cb_collect(#buffers, callback or function() end)
|
||||||
for _, bufnr in ipairs(buffers) do
|
for _, bufnr in ipairs(buffers) do
|
||||||
if hidden_buffers[bufnr] then
|
if hidden_buffers[bufnr] then
|
||||||
vim.b[bufnr].canola_dirty = opts
|
vim.b[bufnr].oil_dirty = opts
|
||||||
-- We also need to mark this as nomodified so it doesn't interfere with quitting vim
|
-- We also need to mark this as nomodified so it doesn't interfere with quitting vim
|
||||||
vim.bo[bufnr].modified = false
|
vim.bo[bufnr].modified = false
|
||||||
vim.schedule(cb)
|
vim.schedule(cb)
|
||||||
|
|
@ -207,19 +207,19 @@ M.set_win_options = function()
|
||||||
local winid = vim.api.nvim_get_current_win()
|
local winid = vim.api.nvim_get_current_win()
|
||||||
|
|
||||||
-- work around https://github.com/neovim/neovim/pull/27422
|
-- work around https://github.com/neovim/neovim/pull/27422
|
||||||
vim.api.nvim_set_option_value('foldmethod', 'manual', { scope = 'local', win = winid })
|
vim.api.nvim_set_option_value("foldmethod", "manual", { scope = "local", win = winid })
|
||||||
|
|
||||||
for k, v in pairs(config.win_options) do
|
for k, v in pairs(config.win_options) do
|
||||||
vim.api.nvim_set_option_value(k, v, { scope = 'local', win = winid })
|
vim.api.nvim_set_option_value(k, v, { scope = "local", win = winid })
|
||||||
end
|
end
|
||||||
if vim.wo[winid].previewwindow then -- apply preview window options last
|
if vim.wo[winid].previewwindow then -- apply preview window options last
|
||||||
for k, v in pairs(config.preview_win.win_options) do
|
for k, v in pairs(config.preview_win.win_options) do
|
||||||
vim.api.nvim_set_option_value(k, v, { scope = 'local', win = winid })
|
vim.api.nvim_set_option_value(k, v, { scope = "local", win = winid })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---Get a list of visible canola buffers and a list of hidden canola buffers
|
---Get a list of visible oil buffers and a list of hidden oil buffers
|
||||||
---@note
|
---@note
|
||||||
--- If any buffers are modified, return values are nil
|
--- If any buffers are modified, return values are nil
|
||||||
---@return nil|integer[] visible
|
---@return nil|integer[] visible
|
||||||
|
|
@ -244,14 +244,14 @@ local function get_visible_hidden_buffers()
|
||||||
return visible_buffers, vim.tbl_keys(hidden_buffers)
|
return visible_buffers, vim.tbl_keys(hidden_buffers)
|
||||||
end
|
end
|
||||||
|
|
||||||
---Delete unmodified, hidden canola buffers and if none remain, clear the cache
|
---Delete unmodified, hidden oil buffers and if none remain, clear the cache
|
||||||
M.delete_hidden_buffers = function()
|
M.delete_hidden_buffers = function()
|
||||||
local visible_buffers, hidden_buffers = get_visible_hidden_buffers()
|
local visible_buffers, hidden_buffers = get_visible_hidden_buffers()
|
||||||
if
|
if
|
||||||
not visible_buffers
|
not visible_buffers
|
||||||
or not hidden_buffers
|
or not hidden_buffers
|
||||||
or not vim.tbl_isempty(visible_buffers)
|
or not vim.tbl_isempty(visible_buffers)
|
||||||
or vim.fn.win_gettype() == 'command'
|
or vim.fn.win_gettype() == "command"
|
||||||
then
|
then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
@ -261,7 +261,7 @@ M.delete_hidden_buffers = function()
|
||||||
cache.clear_everything()
|
cache.clear_everything()
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param adapter canola.Adapter
|
---@param adapter oil.Adapter
|
||||||
---@param ranges table<string, integer[]>
|
---@param ranges table<string, integer[]>
|
||||||
---@return integer
|
---@return integer
|
||||||
local function get_first_mutable_column_col(adapter, ranges)
|
local function get_first_mutable_column_col(adapter, ranges)
|
||||||
|
|
@ -278,20 +278,20 @@ local function get_first_mutable_column_col(adapter, ranges)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- @param bufnr integer
|
--- @param bufnr integer
|
||||||
--- @param adapter canola.Adapter
|
--- @param adapter oil.Adapter
|
||||||
--- @param mode false|"name"|"editable"
|
--- @param mode false|"name"|"editable"
|
||||||
--- @param cur integer[]
|
--- @param cur integer[]
|
||||||
--- @return integer[] | nil
|
--- @return integer[] | nil
|
||||||
local function calc_constrained_cursor_pos(bufnr, adapter, mode, cur)
|
local function calc_constrained_cursor_pos(bufnr, adapter, mode, cur)
|
||||||
local parser = require('canola.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 line = vim.api.nvim_buf_get_lines(bufnr, cur[1] - 1, cur[1], true)[1]
|
||||||
local column_defs = columns.get_supported_columns(adapter)
|
local column_defs = columns.get_supported_columns(adapter)
|
||||||
local result = parser.parse_line(adapter, line, column_defs)
|
local result = parser.parse_line(adapter, line, column_defs)
|
||||||
if result and result.ranges then
|
if result and result.ranges then
|
||||||
local min_col
|
local min_col
|
||||||
if mode == 'editable' then
|
if mode == "editable" then
|
||||||
min_col = get_first_mutable_column_col(adapter, result.ranges)
|
min_col = get_first_mutable_column_col(adapter, result.ranges)
|
||||||
elseif mode == 'name' then
|
elseif mode == "name" then
|
||||||
min_col = result.ranges.name[1]
|
min_col = result.ranges.name[1]
|
||||||
else
|
else
|
||||||
error(string.format('Unexpected value "%s" for option constrain_cursor', mode))
|
error(string.format('Unexpected value "%s" for option constrain_cursor', mode))
|
||||||
|
|
@ -318,7 +318,7 @@ local function constrain_cursor(bufnr, mode)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local mc = package.loaded['multicursor-nvim']
|
local mc = package.loaded["multicursor-nvim"]
|
||||||
if mc then
|
if mc then
|
||||||
mc.onSafeState(function()
|
mc.onSafeState(function()
|
||||||
mc.action(function(ctx)
|
mc.action(function(ctx)
|
||||||
|
|
@ -340,98 +340,20 @@ local function constrain_cursor(bufnr, mode)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param bufnr integer
|
|
||||||
local function show_insert_guide(bufnr)
|
|
||||||
if not config.constrain_cursor 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 cur = vim.api.nvim_win_get_cursor(0)
|
|
||||||
local current_line = vim.api.nvim_buf_get_lines(bufnr, cur[1] - 1, cur[1], true)[1]
|
|
||||||
if current_line ~= '' then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local all_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, true)
|
|
||||||
local ref_line
|
|
||||||
if cur[1] > 1 and all_lines[cur[1] - 1] ~= '' then
|
|
||||||
ref_line = all_lines[cur[1] - 1]
|
|
||||||
elseif cur[1] < #all_lines and all_lines[cur[1] + 1] ~= '' then
|
|
||||||
ref_line = all_lines[cur[1] + 1]
|
|
||||||
else
|
|
||||||
for i, line in ipairs(all_lines) do
|
|
||||||
if line ~= '' and i ~= cur[1] then
|
|
||||||
ref_line = line
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if not ref_line then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local parser = require('canola.mutator.parser')
|
|
||||||
local column_defs = columns.get_supported_columns(adapter)
|
|
||||||
local result = parser.parse_line(adapter, ref_line, column_defs)
|
|
||||||
if not result or not result.ranges then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local id_end = result.ranges.id[2] + 1
|
|
||||||
local col_prefix = ref_line:sub(id_end + 1, result.ranges.name[1])
|
|
||||||
local col_width = vim.api.nvim_strwidth(col_prefix)
|
|
||||||
local id_width
|
|
||||||
local cole = vim.wo.conceallevel
|
|
||||||
if cole >= 2 then
|
|
||||||
id_width = 0
|
|
||||||
elseif cole == 1 then
|
|
||||||
id_width = 1
|
|
||||||
else
|
|
||||||
id_width = vim.api.nvim_strwidth(ref_line:sub(1, id_end))
|
|
||||||
end
|
|
||||||
local virtual_col = id_width + col_width
|
|
||||||
if virtual_col <= 0 then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
vim.w.canola_saved_ve = vim.wo.virtualedit
|
|
||||||
vim.wo.virtualedit = 'all'
|
|
||||||
vim.api.nvim_win_set_cursor(0, { cur[1], virtual_col })
|
|
||||||
|
|
||||||
vim.api.nvim_create_autocmd('TextChangedI', {
|
|
||||||
group = 'Canola',
|
|
||||||
buffer = bufnr,
|
|
||||||
once = true,
|
|
||||||
callback = function()
|
|
||||||
if vim.w.canola_saved_ve ~= nil then
|
|
||||||
vim.wo.virtualedit = vim.w.canola_saved_ve
|
|
||||||
vim.w.canola_saved_ve = nil
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
---Redraw original path virtual text for trash buffer
|
---Redraw original path virtual text for trash buffer
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
local function redraw_trash_virtual_text(bufnr)
|
local function redraw_trash_virtual_text(bufnr)
|
||||||
if not vim.api.nvim_buf_is_valid(bufnr) or not vim.api.nvim_buf_is_loaded(bufnr) then
|
if not vim.api.nvim_buf_is_valid(bufnr) or not vim.api.nvim_buf_is_loaded(bufnr) then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local parser = require('canola.mutator.parser')
|
local parser = require("oil.mutator.parser")
|
||||||
local adapter = util.get_adapter(bufnr, true)
|
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
|
||||||
local _, buf_path = util.parse_url(vim.api.nvim_buf_get_name(bufnr))
|
local _, buf_path = util.parse_url(vim.api.nvim_buf_get_name(bufnr))
|
||||||
local os_path = fs.posix_to_os_path(assert(buf_path))
|
local os_path = fs.posix_to_os_path(assert(buf_path))
|
||||||
local ns = vim.api.nvim_create_namespace('CanolaVtext')
|
local ns = vim.api.nvim_create_namespace("OilVtext")
|
||||||
vim.api.nvim_buf_clear_namespace(bufnr, ns, 0, -1)
|
vim.api.nvim_buf_clear_namespace(bufnr, ns, 0, -1)
|
||||||
local column_defs = columns.get_supported_columns(adapter)
|
local column_defs = columns.get_supported_columns(adapter)
|
||||||
for lnum, line in ipairs(vim.api.nvim_buf_get_lines(bufnr, 0, -1, true)) do
|
for lnum, line in ipairs(vim.api.nvim_buf_get_lines(bufnr, 0, -1, true)) do
|
||||||
|
|
@ -439,14 +361,14 @@ local function redraw_trash_virtual_text(bufnr)
|
||||||
local entry = result and result.entry
|
local entry = result and result.entry
|
||||||
if entry then
|
if entry then
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
---@type nil|canola.TrashInfo
|
---@type nil|oil.TrashInfo
|
||||||
local trash_info = meta and meta.trash_info
|
local trash_info = meta and meta.trash_info
|
||||||
if trash_info then
|
if trash_info then
|
||||||
vim.api.nvim_buf_set_extmark(bufnr, ns, lnum - 1, 0, {
|
vim.api.nvim_buf_set_extmark(bufnr, ns, lnum - 1, 0, {
|
||||||
virt_text = {
|
virt_text = {
|
||||||
{
|
{
|
||||||
'➜ ' .. fs.shorten_path(trash_info.original_path, os_path),
|
"➜ " .. fs.shorten_path(trash_info.original_path, os_path),
|
||||||
'CanolaTrashSourcePath',
|
"OilTrashSourcePath",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -465,13 +387,13 @@ M.initialize = function(bufnr)
|
||||||
end
|
end
|
||||||
vim.api.nvim_clear_autocmds({
|
vim.api.nvim_clear_autocmds({
|
||||||
buffer = bufnr,
|
buffer = bufnr,
|
||||||
group = 'Canola',
|
group = "Oil",
|
||||||
})
|
})
|
||||||
vim.bo[bufnr].buftype = 'acwrite'
|
vim.bo[bufnr].buftype = "acwrite"
|
||||||
vim.bo[bufnr].readonly = false
|
vim.bo[bufnr].readonly = false
|
||||||
vim.bo[bufnr].swapfile = false
|
vim.bo[bufnr].swapfile = false
|
||||||
vim.bo[bufnr].syntax = 'canola'
|
vim.bo[bufnr].syntax = "oil"
|
||||||
vim.bo[bufnr].filetype = 'canola'
|
vim.bo[bufnr].filetype = "oil"
|
||||||
vim.b[bufnr].EditorConfig_disable = 1
|
vim.b[bufnr].EditorConfig_disable = 1
|
||||||
session[bufnr] = session[bufnr] or {}
|
session[bufnr] = session[bufnr] or {}
|
||||||
for k, v in pairs(config.buf_options) do
|
for k, v in pairs(config.buf_options) do
|
||||||
|
|
@ -479,19 +401,19 @@ M.initialize = function(bufnr)
|
||||||
end
|
end
|
||||||
vim.api.nvim_buf_call(bufnr, M.set_win_options)
|
vim.api.nvim_buf_call(bufnr, M.set_win_options)
|
||||||
|
|
||||||
vim.api.nvim_create_autocmd('BufHidden', {
|
vim.api.nvim_create_autocmd("BufHidden", {
|
||||||
desc = 'Delete canola buffers when no longer in use',
|
desc = "Delete oil buffers when no longer in use",
|
||||||
group = 'Canola',
|
group = "Oil",
|
||||||
nested = true,
|
nested = true,
|
||||||
buffer = bufnr,
|
buffer = bufnr,
|
||||||
callback = function()
|
callback = function()
|
||||||
-- First wait a short time (100ms) for the buffer change to settle
|
-- First wait a short time (100ms) for the buffer change to settle
|
||||||
vim.defer_fn(function()
|
vim.defer_fn(function()
|
||||||
local visible_buffers = get_visible_hidden_buffers()
|
local visible_buffers = get_visible_hidden_buffers()
|
||||||
-- Only delete canola buffers if none of them are visible
|
-- Only delete oil buffers if none of them are visible
|
||||||
if visible_buffers and vim.tbl_isempty(visible_buffers) then
|
if visible_buffers and vim.tbl_isempty(visible_buffers) then
|
||||||
-- Check if cleanup is enabled
|
-- Check if cleanup is enabled
|
||||||
if type(config.cleanup_delay_ms) == 'number' then
|
if type(config.cleanup_delay_ms) == "number" then
|
||||||
if config.cleanup_delay_ms > 0 then
|
if config.cleanup_delay_ms > 0 then
|
||||||
vim.defer_fn(function()
|
vim.defer_fn(function()
|
||||||
M.delete_hidden_buffers()
|
M.delete_hidden_buffers()
|
||||||
|
|
@ -504,8 +426,8 @@ M.initialize = function(bufnr)
|
||||||
end, 100)
|
end, 100)
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
vim.api.nvim_create_autocmd('BufUnload', {
|
vim.api.nvim_create_autocmd("BufUnload", {
|
||||||
group = 'Canola',
|
group = "Oil",
|
||||||
nested = true,
|
nested = true,
|
||||||
once = true,
|
once = true,
|
||||||
buffer = bufnr,
|
buffer = bufnr,
|
||||||
|
|
@ -517,47 +439,34 @@ M.initialize = function(bufnr)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
vim.api.nvim_create_autocmd('BufEnter', {
|
vim.api.nvim_create_autocmd("BufEnter", {
|
||||||
group = 'Canola',
|
group = "Oil",
|
||||||
buffer = bufnr,
|
buffer = bufnr,
|
||||||
callback = function(args)
|
callback = function(args)
|
||||||
local opts = vim.b[args.buf].canola_dirty
|
local opts = vim.b[args.buf].oil_dirty
|
||||||
if opts then
|
if opts then
|
||||||
vim.b[args.buf].canola_dirty = nil
|
vim.b[args.buf].oil_dirty = nil
|
||||||
M.render_buffer_async(args.buf, opts)
|
M.render_buffer_async(args.buf, opts)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
local timer
|
local timer
|
||||||
vim.api.nvim_create_autocmd('InsertEnter', {
|
vim.api.nvim_create_autocmd("InsertEnter", {
|
||||||
desc = 'Constrain canola cursor position',
|
desc = "Constrain oil cursor position",
|
||||||
group = 'Canola',
|
group = "Oil",
|
||||||
buffer = bufnr,
|
buffer = 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(function()
|
vim.schedule_wrap(constrain_cursor)(bufnr, config.constrain_cursor)
|
||||||
constrain_cursor(bufnr, config.constrain_cursor)
|
|
||||||
show_insert_guide(bufnr)
|
|
||||||
end)
|
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
vim.api.nvim_create_autocmd('InsertLeave', {
|
vim.api.nvim_create_autocmd({ "CursorMoved", "ModeChanged" }, {
|
||||||
group = 'Canola',
|
desc = "Update oil preview window",
|
||||||
|
group = "Oil",
|
||||||
buffer = bufnr,
|
buffer = bufnr,
|
||||||
callback = function()
|
callback = function()
|
||||||
if vim.w.canola_saved_ve ~= nil then
|
local oil = require("oil")
|
||||||
vim.wo.virtualedit = vim.w.canola_saved_ve
|
|
||||||
vim.w.canola_saved_ve = nil
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
vim.api.nvim_create_autocmd({ 'CursorMoved', 'ModeChanged' }, {
|
|
||||||
desc = 'Update canola preview window',
|
|
||||||
group = 'Canola',
|
|
||||||
buffer = bufnr,
|
|
||||||
callback = function()
|
|
||||||
local canola = require('canola')
|
|
||||||
if vim.wo.previewwindow then
|
if vim.wo.previewwindow then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
@ -582,14 +491,14 @@ M.initialize = function(bufnr)
|
||||||
if vim.api.nvim_get_current_buf() ~= bufnr then
|
if vim.api.nvim_get_current_buf() ~= bufnr then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local entry = canola.get_cursor_entry()
|
local entry = oil.get_cursor_entry()
|
||||||
-- Don't update in visual mode. Visual mode implies editing not browsing,
|
-- Don't update in visual mode. Visual mode implies editing not browsing,
|
||||||
-- and updating the preview can cause flicker and stutter.
|
-- and updating the preview can cause flicker and stutter.
|
||||||
if entry and not util.is_visual_mode() then
|
if entry and not util.is_visual_mode() then
|
||||||
local winid = util.get_preview_win()
|
local winid = util.get_preview_win()
|
||||||
if winid then
|
if winid then
|
||||||
if entry.id ~= vim.w[winid].canola_entry_id then
|
if entry.id ~= vim.w[winid].oil_entry_id then
|
||||||
canola.open_preview()
|
oil.open_preview()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -604,7 +513,7 @@ M.initialize = function(bufnr)
|
||||||
-- Set up a watcher that will refresh the directory
|
-- Set up a watcher that will refresh the directory
|
||||||
if
|
if
|
||||||
adapter
|
adapter
|
||||||
and adapter.name == 'files'
|
and adapter.name == "files"
|
||||||
and config.watch_for_changes
|
and config.watch_for_changes
|
||||||
and not session[bufnr].fs_event
|
and not session[bufnr].fs_event
|
||||||
then
|
then
|
||||||
|
|
@ -623,8 +532,8 @@ M.initialize = function(bufnr)
|
||||||
fs_event:stop()
|
fs_event:stop()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local mutator = require('canola.mutator')
|
local mutator = require("oil.mutator")
|
||||||
if err or vim.bo[bufnr].modified or vim.b[bufnr].canola_dirty or mutator.is_mutating() then
|
if err or vim.bo[bufnr].modified or vim.b[bufnr].oil_dirty or mutator.is_mutating() then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -637,18 +546,18 @@ M.initialize = function(bufnr)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- If it is not currently visible, mark it as dirty
|
-- If it is not currently visible, mark it as dirty
|
||||||
vim.b[bufnr].canola_dirty = {}
|
vim.b[bufnr].oil_dirty = {}
|
||||||
end)
|
end)
|
||||||
)
|
)
|
||||||
session[bufnr].fs_event = fs_event
|
session[bufnr].fs_event = fs_event
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Watch for TextChanged and update the trash original path extmarks
|
-- Watch for TextChanged and update the trash original path extmarks
|
||||||
if adapter and adapter.name == 'trash' then
|
if adapter and adapter.name == "trash" then
|
||||||
local debounce_timer = assert(uv.new_timer())
|
local debounce_timer = assert(uv.new_timer())
|
||||||
local pending = false
|
local pending = false
|
||||||
vim.api.nvim_create_autocmd('TextChanged', {
|
vim.api.nvim_create_autocmd("TextChanged", {
|
||||||
desc = 'Update canola virtual text of original path',
|
desc = "Update oil virtual text of original path",
|
||||||
buffer = bufnr,
|
buffer = bufnr,
|
||||||
callback = function()
|
callback = function()
|
||||||
-- Respond immediately to prevent flickering, the set the timer for a "cooldown period"
|
-- Respond immediately to prevent flickering, the set the timer for a "cooldown period"
|
||||||
|
|
@ -674,35 +583,35 @@ M.initialize = function(bufnr)
|
||||||
M.render_buffer_async(bufnr, {}, function(err)
|
M.render_buffer_async(bufnr, {}, function(err)
|
||||||
if err then
|
if err then
|
||||||
vim.notify(
|
vim.notify(
|
||||||
string.format('Error rendering canola buffer %s: %s', vim.api.nvim_buf_get_name(bufnr), err),
|
string.format("Error rendering oil buffer %s: %s", vim.api.nvim_buf_get_name(bufnr), err),
|
||||||
vim.log.levels.ERROR
|
vim.log.levels.ERROR
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
vim.b[bufnr].canola_ready = true
|
vim.b[bufnr].oil_ready = true
|
||||||
vim.api.nvim_exec_autocmds(
|
vim.api.nvim_exec_autocmds(
|
||||||
'User',
|
"User",
|
||||||
{ pattern = 'CanolaEnter', modeline = false, data = { buf = bufnr } }
|
{ pattern = "OilEnter", modeline = false, data = { buf = bufnr } }
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
keymap_util.set_keymaps(config.keymaps, bufnr)
|
keymap_util.set_keymaps(config.keymaps, bufnr)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param adapter canola.Adapter
|
---@param adapter oil.Adapter
|
||||||
---@param num_entries integer
|
---@param num_entries integer
|
||||||
---@return fun(a: canola.InternalEntry, b: canola.InternalEntry): boolean
|
---@return fun(a: oil.InternalEntry, b: oil.InternalEntry): boolean
|
||||||
local function get_sort_function(adapter, num_entries)
|
local function get_sort_function(adapter, num_entries)
|
||||||
local idx_funs = {}
|
local idx_funs = {}
|
||||||
local sort_config = config.view_options.sort
|
local sort_config = config.view_options.sort
|
||||||
|
|
||||||
-- If empty, default to type + name sorting
|
-- If empty, default to type + name sorting
|
||||||
if vim.tbl_isempty(sort_config) then
|
if vim.tbl_isempty(sort_config) then
|
||||||
sort_config = { { 'type', 'asc' }, { 'name', 'asc' } }
|
sort_config = { { "type", "asc" }, { "name", "asc" } }
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, sort_pair in ipairs(sort_config) do
|
for _, sort_pair in ipairs(sort_config) do
|
||||||
local col_name, order = unpack(sort_pair)
|
local col_name, order = unpack(sort_pair)
|
||||||
if order ~= 'asc' and order ~= 'desc' then
|
if order ~= "asc" and order ~= "desc" then
|
||||||
vim.notify_once(
|
vim.notify_once(
|
||||||
string.format(
|
string.format(
|
||||||
"Column '%s' has invalid sort order '%s'. Should be either 'asc' or 'desc'",
|
"Column '%s' has invalid sort order '%s'. Should be either 'asc' or 'desc'",
|
||||||
|
|
@ -730,7 +639,7 @@ local function get_sort_function(adapter, num_entries)
|
||||||
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
|
||||||
if order == 'desc' then
|
if order == "desc" then
|
||||||
return a_val > b_val
|
return a_val > b_val
|
||||||
else
|
else
|
||||||
return a_val < b_val
|
return a_val < b_val
|
||||||
|
|
@ -754,7 +663,7 @@ local function render_buffer(bufnr, opts)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
||||||
opts = vim.tbl_extend('keep', opts or {}, {
|
opts = vim.tbl_extend("keep", opts or {}, {
|
||||||
jump = false,
|
jump = false,
|
||||||
jump_first = false,
|
jump_first = false,
|
||||||
})
|
})
|
||||||
|
|
@ -767,7 +676,7 @@ local function render_buffer(bufnr, opts)
|
||||||
local entry_list = vim.tbl_values(entries)
|
local entry_list = vim.tbl_values(entries)
|
||||||
|
|
||||||
-- Only sort the entries once we have them all
|
-- Only sort the entries once we have them all
|
||||||
if not vim.b[bufnr].canola_rendering then
|
if not vim.b[bufnr].oil_rendering then
|
||||||
table.sort(entry_list, get_sort_function(adapter, #entry_list))
|
table.sort(entry_list, get_sort_function(adapter, #entry_list))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -784,10 +693,10 @@ local function render_buffer(bufnr, opts)
|
||||||
for i, col_def in ipairs(column_defs) do
|
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)
|
local _, conf = util.split_config(col_def)
|
||||||
col_align[i + 1] = conf and conf.align or 'left'
|
col_align[i + 1] = conf and conf.align or "left"
|
||||||
end
|
end
|
||||||
|
|
||||||
local parent_entry = { 0, '..', 'directory' }
|
local parent_entry = { 0, "..", "directory" }
|
||||||
if M.should_display(bufnr, parent_entry) then
|
if M.should_display(bufnr, parent_entry) then
|
||||||
local cols = M.format_entry_cols(parent_entry, column_defs, col_width, adapter, true, bufnr)
|
local cols = M.format_entry_cols(parent_entry, column_defs, col_width, adapter, true, bufnr)
|
||||||
table.insert(line_table, cols)
|
table.insert(line_table, cols)
|
||||||
|
|
@ -807,21 +716,6 @@ local function render_buffer(bufnr, opts)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if config.view_options.show_hidden_when_empty and #line_table <= 1 then
|
|
||||||
for _, entry in ipairs(entry_list) do
|
|
||||||
local name = entry[FIELD_NAME]
|
|
||||||
local public_entry = util.export_entry(entry)
|
|
||||||
if not config.view_options.is_always_hidden(name, bufnr, public_entry) then
|
|
||||||
local cols = M.format_entry_cols(entry, column_defs, col_width, adapter, true, bufnr)
|
|
||||||
table.insert(line_table, cols)
|
|
||||||
if seek_after_render == name then
|
|
||||||
seek_after_render_found = true
|
|
||||||
jump_idx = #line_table
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local lines, highlights = util.render_table(line_table, col_width, col_align)
|
local lines, highlights = util.render_table(line_table, col_width, col_align)
|
||||||
|
|
||||||
vim.bo[bufnr].modifiable = true
|
vim.bo[bufnr].modifiable = true
|
||||||
|
|
@ -838,7 +732,7 @@ local function render_buffer(bufnr, opts)
|
||||||
if jump_idx then
|
if jump_idx then
|
||||||
local lnum = jump_idx
|
local lnum = jump_idx
|
||||||
local line = vim.api.nvim_buf_get_lines(bufnr, lnum - 1, lnum, true)[1]
|
local line = vim.api.nvim_buf_get_lines(bufnr, lnum - 1, lnum, true)[1]
|
||||||
local id_str = line:match('^/(%d+)')
|
local id_str = line:match("^/(%d+)")
|
||||||
local id = tonumber(id_str)
|
local id = tonumber(id_str)
|
||||||
if id then
|
if id then
|
||||||
local entry = cache.get_entry_by_id(id)
|
local entry = cache.get_entry_by_id(id)
|
||||||
|
|
@ -851,7 +745,7 @@ local function render_buffer(bufnr, opts)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
constrain_cursor(bufnr, 'name')
|
constrain_cursor(bufnr, "name")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
@ -866,13 +760,13 @@ end
|
||||||
local function get_link_text(name, meta)
|
local function get_link_text(name, meta)
|
||||||
local link_text
|
local link_text
|
||||||
if meta then
|
if meta then
|
||||||
if meta.link_stat and meta.link_stat.type == 'directory' then
|
if meta.link_stat and meta.link_stat.type == "directory" then
|
||||||
name = name .. '/'
|
name = name .. "/"
|
||||||
end
|
end
|
||||||
|
|
||||||
if meta.link then
|
if meta.link then
|
||||||
link_text = '-> ' .. meta.link:gsub('\n', '')
|
link_text = "-> " .. meta.link:gsub("\n", "")
|
||||||
if meta.link_stat and meta.link_stat.type == 'directory' then
|
if meta.link_stat and meta.link_stat.type == "directory" then
|
||||||
link_text = util.addslash(link_text)
|
link_text = util.addslash(link_text)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -882,25 +776,25 @@ local function get_link_text(name, meta)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@private
|
---@private
|
||||||
---@param entry canola.InternalEntry
|
---@param entry oil.InternalEntry
|
||||||
---@param column_defs table[]
|
---@param column_defs table[]
|
||||||
---@param col_width integer[]
|
---@param col_width integer[]
|
||||||
---@param adapter canola.Adapter
|
---@param adapter oil.Adapter
|
||||||
---@param is_hidden boolean
|
---@param is_hidden boolean
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
---@return canola.TextChunk[]
|
---@return oil.TextChunk[]
|
||||||
M.format_entry_cols = function(entry, column_defs, col_width, adapter, is_hidden, bufnr)
|
M.format_entry_cols = function(entry, column_defs, col_width, adapter, is_hidden, bufnr)
|
||||||
local name = entry[FIELD_NAME]
|
local name = entry[FIELD_NAME]
|
||||||
local meta = entry[FIELD_META]
|
local meta = entry[FIELD_META]
|
||||||
local hl_suffix = ''
|
local hl_suffix = ""
|
||||||
if is_hidden then
|
if is_hidden then
|
||||||
hl_suffix = 'Hidden'
|
hl_suffix = "Hidden"
|
||||||
end
|
end
|
||||||
if meta and meta.display_name then
|
if meta and meta.display_name then
|
||||||
name = meta.display_name
|
name = meta.display_name
|
||||||
end
|
end
|
||||||
-- We can't handle newlines in filenames (and shame on you for doing that)
|
-- We can't handle newlines in filenames (and shame on you for doing that)
|
||||||
name = name:gsub('\n', '')
|
name = name:gsub("\n", "")
|
||||||
-- First put the unique ID
|
-- First put the unique ID
|
||||||
local cols = {}
|
local cols = {}
|
||||||
local id_key = cache.format_id(entry[FIELD_ID])
|
local id_key = cache.format_id(entry[FIELD_ID])
|
||||||
|
|
@ -909,7 +803,7 @@ M.format_entry_cols = function(entry, column_defs, col_width, adapter, is_hidden
|
||||||
-- Then add all the configured columns
|
-- Then add all the configured columns
|
||||||
for i, column in ipairs(column_defs) do
|
for i, column in ipairs(column_defs) do
|
||||||
local chunk = columns.render_col(adapter, column, entry, bufnr)
|
local chunk = columns.render_col(adapter, column, entry, bufnr)
|
||||||
local text = type(chunk) == 'table' and chunk[1] or chunk
|
local text = type(chunk) == "table" and chunk[1] or chunk
|
||||||
---@cast text string
|
---@cast text string
|
||||||
col_width[i + 1] = math.max(col_width[i + 1], vim.api.nvim_strwidth(text))
|
col_width[i + 1] = math.max(col_width[i + 1], vim.api.nvim_strwidth(text))
|
||||||
table.insert(cols, chunk)
|
table.insert(cols, chunk)
|
||||||
|
|
@ -922,7 +816,7 @@ M.format_entry_cols = function(entry, column_defs, col_width, adapter, is_hidden
|
||||||
if get_custom_hl then
|
if get_custom_hl then
|
||||||
local external_entry = util.export_entry(entry)
|
local external_entry = util.export_entry(entry)
|
||||||
|
|
||||||
if entry_type == 'link' then
|
if entry_type == "link" then
|
||||||
link_name, link_target = get_link_text(name, meta)
|
link_name, link_target = get_link_text(name, meta)
|
||||||
local is_orphan = not (meta and meta.link_stat)
|
local is_orphan = not (meta and meta.link_stat)
|
||||||
link_name_hl = get_custom_hl(external_entry, is_hidden, false, is_orphan, bufnr)
|
link_name_hl = get_custom_hl(external_entry, is_hidden, false, is_orphan, bufnr)
|
||||||
|
|
@ -936,8 +830,8 @@ M.format_entry_cols = function(entry, column_defs, col_width, adapter, is_hidden
|
||||||
local hl = get_custom_hl(external_entry, is_hidden, false, false, bufnr)
|
local hl = get_custom_hl(external_entry, is_hidden, false, false, bufnr)
|
||||||
if hl then
|
if hl then
|
||||||
-- Add the trailing / if this is a directory, this is important
|
-- Add the trailing / if this is a directory, this is important
|
||||||
if entry_type == 'directory' then
|
if entry_type == "directory" then
|
||||||
name = name .. '/'
|
name = name .. "/"
|
||||||
end
|
end
|
||||||
table.insert(cols, { name, hl })
|
table.insert(cols, { name, hl })
|
||||||
return cols
|
return cols
|
||||||
|
|
@ -946,50 +840,49 @@ M.format_entry_cols = function(entry, column_defs, col_width, adapter, is_hidden
|
||||||
end
|
end
|
||||||
|
|
||||||
local highlight_as_executable = false
|
local highlight_as_executable = false
|
||||||
if entry_type ~= 'directory' then
|
if entry_type ~= "directory" then
|
||||||
local lower = name:lower()
|
local lower = name:lower()
|
||||||
if
|
if
|
||||||
lower:match('%.exe$')
|
lower:match("%.exe$")
|
||||||
or lower:match('%.bat$')
|
or lower:match("%.bat$")
|
||||||
or lower:match('%.cmd$')
|
or lower:match("%.cmd$")
|
||||||
or lower:match('%.com$')
|
or lower:match("%.com$")
|
||||||
or lower:match('%.ps1$')
|
or lower:match("%.ps1$")
|
||||||
then
|
then
|
||||||
highlight_as_executable = true
|
highlight_as_executable = true
|
||||||
-- selene: allow(if_same_then_else)
|
|
||||||
elseif is_unix_executable(entry) then
|
elseif is_unix_executable(entry) then
|
||||||
highlight_as_executable = true
|
highlight_as_executable = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if entry_type == 'directory' then
|
if entry_type == "directory" then
|
||||||
table.insert(cols, { name .. '/', 'CanolaDir' .. hl_suffix })
|
table.insert(cols, { name .. "/", "OilDir" .. hl_suffix })
|
||||||
elseif entry_type == 'socket' then
|
elseif entry_type == "socket" then
|
||||||
table.insert(cols, { name, 'CanolaSocket' .. hl_suffix })
|
table.insert(cols, { name, "OilSocket" .. hl_suffix })
|
||||||
elseif entry_type == 'link' then
|
elseif entry_type == "link" then
|
||||||
if not link_name then
|
if not link_name then
|
||||||
link_name, link_target = get_link_text(name, meta)
|
link_name, link_target = get_link_text(name, meta)
|
||||||
end
|
end
|
||||||
local is_orphan = not (meta and meta.link_stat)
|
local is_orphan = not (meta and meta.link_stat)
|
||||||
if not link_name_hl then
|
if not link_name_hl then
|
||||||
if highlight_as_executable then
|
if highlight_as_executable then
|
||||||
link_name_hl = 'CanolaExecutable' .. hl_suffix
|
link_name_hl = "OilExecutable" .. hl_suffix
|
||||||
else
|
else
|
||||||
link_name_hl = (is_orphan and 'CanolaOrphanLink' or 'CanolaLink') .. hl_suffix
|
link_name_hl = (is_orphan and "OilOrphanLink" or "OilLink") .. hl_suffix
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
table.insert(cols, { link_name, link_name_hl })
|
table.insert(cols, { link_name, link_name_hl })
|
||||||
|
|
||||||
if link_target then
|
if link_target then
|
||||||
if not link_target_hl then
|
if not link_target_hl then
|
||||||
link_target_hl = (is_orphan and 'CanolaOrphanLinkTarget' or 'CanolaLinkTarget') .. hl_suffix
|
link_target_hl = (is_orphan and "OilOrphanLinkTarget" or "OilLinkTarget") .. hl_suffix
|
||||||
end
|
end
|
||||||
table.insert(cols, { link_target, link_target_hl })
|
table.insert(cols, { link_target, link_target_hl })
|
||||||
end
|
end
|
||||||
elseif highlight_as_executable then
|
elseif highlight_as_executable then
|
||||||
table.insert(cols, { name, 'CanolaExecutable' .. hl_suffix })
|
table.insert(cols, { name, "OilExecutable" .. hl_suffix })
|
||||||
else
|
else
|
||||||
table.insert(cols, { name, 'CanolaFile' .. hl_suffix })
|
table.insert(cols, { name, "OilFile" .. hl_suffix })
|
||||||
end
|
end
|
||||||
|
|
||||||
return cols
|
return cols
|
||||||
|
|
@ -1021,8 +914,8 @@ M.render_buffer_async = function(bufnr, opts, caller_callback)
|
||||||
local function callback(err)
|
local function callback(err)
|
||||||
if not err then
|
if not err then
|
||||||
vim.api.nvim_exec_autocmds(
|
vim.api.nvim_exec_autocmds(
|
||||||
'User',
|
"User",
|
||||||
{ pattern = 'CanolaReadPost', modeline = false, data = { buf = bufnr } }
|
{ pattern = "OilReadPost", modeline = false, data = { buf = bufnr } }
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
if caller_callback then
|
if caller_callback then
|
||||||
|
|
@ -1030,7 +923,7 @@ M.render_buffer_async = function(bufnr, opts, caller_callback)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
opts = vim.tbl_deep_extend('keep', opts or {}, {
|
opts = vim.tbl_deep_extend("keep", opts or {}, {
|
||||||
refetch = true,
|
refetch = true,
|
||||||
})
|
})
|
||||||
---@cast opts -nil
|
---@cast opts -nil
|
||||||
|
|
@ -1039,7 +932,7 @@ M.render_buffer_async = function(bufnr, opts, caller_callback)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- If we're already rendering, queue up another rerender after it's complete
|
-- If we're already rendering, queue up another rerender after it's complete
|
||||||
if vim.b[bufnr].canola_rendering then
|
if vim.b[bufnr].oil_rendering then
|
||||||
if not pending_renders[bufnr] then
|
if not pending_renders[bufnr] then
|
||||||
pending_renders[bufnr] = { callback }
|
pending_renders[bufnr] = { callback }
|
||||||
elseif callback then
|
elseif callback then
|
||||||
|
|
@ -1049,15 +942,15 @@ M.render_buffer_async = function(bufnr, opts, caller_callback)
|
||||||
end
|
end
|
||||||
|
|
||||||
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
||||||
vim.b[bufnr].canola_rendering = true
|
vim.b[bufnr].oil_rendering = true
|
||||||
local _, dir = util.parse_url(bufname)
|
local _, dir = util.parse_url(bufname)
|
||||||
-- Undo should not return to a blank buffer
|
-- Undo should not return to a blank buffer
|
||||||
-- Method taken from :h clear-undo
|
-- Method taken from :h clear-undo
|
||||||
vim.bo[bufnr].undolevels = -1
|
vim.bo[bufnr].undolevels = -1
|
||||||
local handle_error = vim.schedule_wrap(function(message)
|
local handle_error = vim.schedule_wrap(function(message)
|
||||||
vim.b[bufnr].canola_rendering = false
|
vim.b[bufnr].oil_rendering = false
|
||||||
vim.bo[bufnr].undolevels = vim.api.nvim_get_option_value('undolevels', { scope = 'global' })
|
vim.bo[bufnr].undolevels = vim.api.nvim_get_option_value("undolevels", { scope = "global" })
|
||||||
util.render_text(bufnr, { 'Error: ' .. message })
|
util.render_text(bufnr, { "Error: " .. message })
|
||||||
if pending_renders[bufnr] then
|
if pending_renders[bufnr] then
|
||||||
for _, cb in ipairs(pending_renders[bufnr]) do
|
for _, cb in ipairs(pending_renders[bufnr]) do
|
||||||
cb(message)
|
cb(message)
|
||||||
|
|
@ -1071,12 +964,12 @@ M.render_buffer_async = function(bufnr, opts, caller_callback)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
if not dir then
|
if not dir then
|
||||||
handle_error(string.format("Could not parse canola url '%s'", bufname))
|
handle_error(string.format("Could not parse oil url '%s'", bufname))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local adapter = util.get_adapter(bufnr, true)
|
local adapter = util.get_adapter(bufnr, true)
|
||||||
if not adapter then
|
if not adapter then
|
||||||
handle_error(string.format("[canola] no adapter for buffer '%s'", bufname))
|
handle_error(string.format("[oil] no adapter for buffer '%s'", bufname))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local start_ms = uv.hrtime() / 1e6
|
local start_ms = uv.hrtime() / 1e6
|
||||||
|
|
@ -1090,11 +983,11 @@ M.render_buffer_async = function(bufnr, opts, caller_callback)
|
||||||
if not vim.api.nvim_buf_is_valid(bufnr) then
|
if not vim.api.nvim_buf_is_valid(bufnr) then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
vim.b[bufnr].canola_rendering = false
|
vim.b[bufnr].oil_rendering = false
|
||||||
loading.set_loading(bufnr, false)
|
loading.set_loading(bufnr, false)
|
||||||
render_buffer(bufnr, { jump = true })
|
render_buffer(bufnr, { jump = true })
|
||||||
M.set_last_cursor(bufname, nil)
|
M.set_last_cursor(bufname, nil)
|
||||||
vim.bo[bufnr].undolevels = vim.api.nvim_get_option_value('undolevels', { scope = 'global' })
|
vim.bo[bufnr].undolevels = vim.api.nvim_get_option_value("undolevels", { scope = "global" })
|
||||||
vim.bo[bufnr].modifiable = not buffers_locked and adapter.is_modifiable(bufnr)
|
vim.bo[bufnr].modifiable = not buffers_locked and adapter.is_modifiable(bufnr)
|
||||||
if callback then
|
if callback then
|
||||||
callback()
|
callback()
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
M.is_win_supported = function(winid, bufnr)
|
M.is_win_supported = function(winid, bufnr)
|
||||||
return vim.bo[bufnr].filetype == 'canola'
|
return vim.bo[bufnr].filetype == "oil"
|
||||||
end
|
end
|
||||||
|
|
||||||
M.save_win = function(winid)
|
M.save_win = function(winid)
|
||||||
|
|
@ -11,7 +11,7 @@ M.save_win = function(winid)
|
||||||
end
|
end
|
||||||
|
|
||||||
M.load_win = function(winid, config)
|
M.load_win = function(winid, config)
|
||||||
require('canola').open(config.bufname)
|
require("oil").open(config.bufname)
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
vim.opt.runtimepath:prepend('scripts/benchmark.nvim')
|
vim.opt.runtimepath:prepend("scripts/benchmark.nvim")
|
||||||
vim.opt.runtimepath:prepend('.')
|
vim.opt.runtimepath:prepend(".")
|
||||||
|
|
||||||
local bm = require('benchmark')
|
local bm = require("benchmark")
|
||||||
bm.sandbox()
|
bm.sandbox()
|
||||||
|
|
||||||
---@module 'canola'
|
---@module 'oil'
|
||||||
---@type canola.SetupOpts
|
---@type oil.SetupOpts
|
||||||
local setup_opts = {
|
local setup_opts = {
|
||||||
-- columns = { "icon", "permissions", "size", "mtime" },
|
-- columns = { "icon", "permissions", "size", "mtime" },
|
||||||
}
|
}
|
||||||
|
|
@ -14,53 +14,50 @@ local DIR_SIZE = tonumber(vim.env.DIR_SIZE) or 100000
|
||||||
local ITERATIONS = tonumber(vim.env.ITERATIONS) or 10
|
local ITERATIONS = tonumber(vim.env.ITERATIONS) or 10
|
||||||
local WARM_UP = tonumber(vim.env.WARM_UP) or 1
|
local WARM_UP = tonumber(vim.env.WARM_UP) or 1
|
||||||
local OUTLIERS = tonumber(vim.env.OUTLIERS) or math.floor(ITERATIONS / 10)
|
local OUTLIERS = tonumber(vim.env.OUTLIERS) or math.floor(ITERATIONS / 10)
|
||||||
local TEST_DIR = 'perf/tmp/test_' .. DIR_SIZE
|
local TEST_DIR = "perf/tmp/test_" .. DIR_SIZE
|
||||||
|
|
||||||
vim.fn.mkdir(TEST_DIR, 'p')
|
vim.fn.mkdir(TEST_DIR, "p")
|
||||||
require('benchmark.files').create_files(TEST_DIR, 'file %d.txt', DIR_SIZE)
|
require("benchmark.files").create_files(TEST_DIR, "file %d.txt", DIR_SIZE)
|
||||||
|
|
||||||
-- selene: allow(global_usage)
|
|
||||||
function _G.jit_profile()
|
function _G.jit_profile()
|
||||||
require('canola').setup(setup_opts)
|
require("oil").setup(setup_opts)
|
||||||
local finish = bm.jit_profile({ filename = TEST_DIR .. '/profile.txt' })
|
local finish = bm.jit_profile({ filename = TEST_DIR .. "/profile.txt" })
|
||||||
bm.wait_for_user_event('CanolaEnter', function()
|
bm.wait_for_user_event("OilEnter", function()
|
||||||
finish()
|
finish()
|
||||||
end)
|
end)
|
||||||
require('canola').open(TEST_DIR)
|
require("oil").open(TEST_DIR)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- selene: allow(global_usage)
|
|
||||||
function _G.flame_profile()
|
function _G.flame_profile()
|
||||||
local start, stop = bm.flame_profile({
|
local start, stop = bm.flame_profile({
|
||||||
pattern = 'canola*',
|
pattern = "oil*",
|
||||||
filename = 'profile.json',
|
filename = "profile.json",
|
||||||
})
|
})
|
||||||
require('canola').setup(setup_opts)
|
require("oil").setup(setup_opts)
|
||||||
start()
|
start()
|
||||||
bm.wait_for_user_event('CanolaEnter', function()
|
bm.wait_for_user_event("OilEnter", function()
|
||||||
stop(function()
|
stop(function()
|
||||||
vim.cmd.qall({ mods = { silent = true } })
|
vim.cmd.qall({ mods = { silent = true } })
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
require('canola').open(TEST_DIR)
|
require("oil").open(TEST_DIR)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- selene: allow(global_usage)
|
|
||||||
function _G.benchmark()
|
function _G.benchmark()
|
||||||
require('canola').setup(setup_opts)
|
require("oil").setup(setup_opts)
|
||||||
bm.run({ title = 'canola.nvim', iterations = ITERATIONS, warm_up = WARM_UP }, function(callback)
|
bm.run({ title = "oil.nvim", iterations = ITERATIONS, warm_up = WARM_UP }, function(callback)
|
||||||
bm.wait_for_user_event('CanolaEnter', callback)
|
bm.wait_for_user_event("OilEnter", callback)
|
||||||
require('canola').open(TEST_DIR)
|
require("oil").open(TEST_DIR)
|
||||||
end, function(times)
|
end, function(times)
|
||||||
local avg = bm.avg(times, { trim_outliers = OUTLIERS })
|
local avg = bm.avg(times, { trim_outliers = OUTLIERS })
|
||||||
local std_dev = bm.std_dev(times, { trim_outliers = OUTLIERS })
|
local std_dev = bm.std_dev(times, { trim_outliers = OUTLIERS })
|
||||||
local lines = {
|
local lines = {
|
||||||
table.concat(vim.tbl_map(bm.format_time, times), ' '),
|
table.concat(vim.tbl_map(bm.format_time, times), " "),
|
||||||
string.format('Average: %s', bm.format_time(avg)),
|
string.format("Average: %s", bm.format_time(avg)),
|
||||||
string.format('Std deviation: %s', bm.format_time(std_dev)),
|
string.format("Std deviation: %s", bm.format_time(std_dev)),
|
||||||
}
|
}
|
||||||
|
|
||||||
vim.fn.writefile(lines, 'perf/tmp/benchmark.txt')
|
vim.fn.writefile(lines, "perf/tmp/benchmark.txt")
|
||||||
vim.cmd.qall({ mods = { silent = true } })
|
vim.cmd.qall({ mods = { silent = true } })
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
if vim.g.canola ~= nil then
|
|
||||||
require('canola').setup()
|
|
||||||
end
|
|
||||||
3
plugin/oil.lua
Normal file
3
plugin/oil.lua
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
if vim.g.oil ~= nil then
|
||||||
|
require("oil").setup()
|
||||||
|
end
|
||||||
24
run_tests.sh
Executable file
24
run_tests.sh
Executable file
|
|
@ -0,0 +1,24 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
mkdir -p ".testenv/config/nvim"
|
||||||
|
mkdir -p ".testenv/data/nvim"
|
||||||
|
mkdir -p ".testenv/state/nvim"
|
||||||
|
mkdir -p ".testenv/run/nvim"
|
||||||
|
mkdir -p ".testenv/cache/nvim"
|
||||||
|
PLUGINS=".testenv/data/nvim/site/pack/plugins/start"
|
||||||
|
|
||||||
|
if [ ! -e "$PLUGINS/plenary.nvim" ]; then
|
||||||
|
git clone --depth=1 https://github.com/nvim-lua/plenary.nvim.git "$PLUGINS/plenary.nvim"
|
||||||
|
else
|
||||||
|
(cd "$PLUGINS/plenary.nvim" && git pull)
|
||||||
|
fi
|
||||||
|
|
||||||
|
XDG_CONFIG_HOME=".testenv/config" \
|
||||||
|
XDG_DATA_HOME=".testenv/data" \
|
||||||
|
XDG_STATE_HOME=".testenv/state" \
|
||||||
|
XDG_RUNTIME_DIR=".testenv/run" \
|
||||||
|
XDG_CACHE_HOME=".testenv/cache" \
|
||||||
|
nvim --headless -u tests/minimal_init.lua \
|
||||||
|
-c "PlenaryBustedDirectory ${1-tests} { minimal_init = './tests/minimal_init.lua' }"
|
||||||
|
echo "Success"
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
nix develop --command stylua --check lua spec
|
|
||||||
git ls-files '*.lua' | xargs nix develop --command selene --display-style quiet
|
|
||||||
nix develop --command prettier --check .
|
|
||||||
nix fmt
|
|
||||||
git diff --exit-code -- '*.nix'
|
|
||||||
nix develop --command busted
|
|
||||||
|
|
@ -1,7 +1 @@
|
||||||
std = 'vim'
|
std = 'vim'
|
||||||
exclude = [".direnv/*"]
|
|
||||||
|
|
||||||
[lints]
|
|
||||||
mixed_table = 'allow'
|
|
||||||
unused_variable = 'allow'
|
|
||||||
bad_string_escape = 'allow'
|
|
||||||
|
|
|
||||||
|
|
@ -1,150 +0,0 @@
|
||||||
local canola = require('canola')
|
|
||||||
local fs = require('canola.fs')
|
|
||||||
local test_util = require('spec.test_util')
|
|
||||||
|
|
||||||
describe('Alternate buffer', function()
|
|
||||||
after_each(function()
|
|
||||||
test_util.reset_editor()
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('sets previous buffer as alternate', function()
|
|
||||||
vim.cmd.edit({ args = { 'foo' } })
|
|
||||||
canola.open()
|
|
||||||
test_util.wait_for_autocmd({ 'User', pattern = 'CanolaEnter' })
|
|
||||||
vim.cmd.edit({ args = { 'bar' } })
|
|
||||||
assert.equals('foo', vim.fn.expand('#'))
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('sets previous buffer as alternate when editing url file', function()
|
|
||||||
vim.cmd.edit({ args = { 'foo' } })
|
|
||||||
canola.open()
|
|
||||||
test_util.wait_for_autocmd({ 'User', pattern = 'CanolaEnter' })
|
|
||||||
local readme = fs.join(vim.fn.getcwd(), 'README.md')
|
|
||||||
vim.cmd.edit({ args = { 'canola://' .. fs.os_to_posix_path(readme) } })
|
|
||||||
test_util.wait_for_autocmd('BufEnter')
|
|
||||||
assert.equals(readme, vim.api.nvim_buf_get_name(0))
|
|
||||||
assert.equals('foo', vim.fn.expand('#'))
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('sets previous buffer as alternate when editing canola://', function()
|
|
||||||
vim.cmd.edit({ args = { 'foo' } })
|
|
||||||
vim.cmd.edit({ args = { 'canola://' .. fs.os_to_posix_path(vim.fn.getcwd()) } })
|
|
||||||
test_util.wait_for_autocmd({ 'User', pattern = 'CanolaEnter' })
|
|
||||||
vim.cmd.edit({ args = { 'bar' } })
|
|
||||||
assert.equals('foo', vim.fn.expand('#'))
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('preserves alternate buffer if editing the same file', function()
|
|
||||||
vim.cmd.edit({ args = { 'foo' } })
|
|
||||||
vim.cmd.edit({ args = { 'bar' } })
|
|
||||||
canola.open()
|
|
||||||
test_util.wait_for_autocmd({ 'User', pattern = 'CanolaEnter' })
|
|
||||||
vim.cmd.edit({ args = { 'bar' } })
|
|
||||||
assert.equals('foo', vim.fn.expand('#'))
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('preserves alternate buffer if discarding changes', function()
|
|
||||||
vim.cmd.edit({ args = { 'foo' } })
|
|
||||||
vim.cmd.edit({ args = { 'bar' } })
|
|
||||||
canola.open()
|
|
||||||
test_util.wait_for_autocmd({ 'User', pattern = 'CanolaEnter' })
|
|
||||||
canola.close()
|
|
||||||
assert.equals('bar', vim.fn.expand('%'))
|
|
||||||
assert.equals('foo', vim.fn.expand('#'))
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('sets previous buffer as alternate after multi-dir hops', function()
|
|
||||||
vim.cmd.edit({ args = { 'foo' } })
|
|
||||||
canola.open()
|
|
||||||
test_util.wait_for_autocmd({ 'User', pattern = 'CanolaEnter' })
|
|
||||||
canola.open()
|
|
||||||
test_util.wait_for_autocmd({ 'User', pattern = 'CanolaEnter' })
|
|
||||||
canola.open()
|
|
||||||
test_util.wait_for_autocmd({ 'User', pattern = 'CanolaEnter' })
|
|
||||||
canola.open()
|
|
||||||
test_util.wait_for_autocmd({ 'User', pattern = 'CanolaEnter' })
|
|
||||||
vim.cmd.edit({ args = { 'bar' } })
|
|
||||||
assert.equals('foo', vim.fn.expand('#'))
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('sets previous buffer as alternate when inside canola buffer', function()
|
|
||||||
vim.cmd.edit({ args = { 'foo' } })
|
|
||||||
canola.open()
|
|
||||||
test_util.wait_for_autocmd({ 'User', pattern = 'CanolaEnter' })
|
|
||||||
assert.equals('foo', vim.fn.expand('#'))
|
|
||||||
vim.cmd.edit({ args = { 'bar' } })
|
|
||||||
assert.equals('foo', vim.fn.expand('#'))
|
|
||||||
canola.open()
|
|
||||||
assert.equals('bar', vim.fn.expand('#'))
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('preserves alternate when traversing canola dirs', function()
|
|
||||||
vim.cmd.edit({ args = { 'foo' } })
|
|
||||||
canola.open()
|
|
||||||
test_util.wait_for_autocmd({ 'User', pattern = 'CanolaEnter' })
|
|
||||||
assert.equals('foo', vim.fn.expand('#'))
|
|
||||||
vim.wait(1000, function()
|
|
||||||
return canola.get_cursor_entry()
|
|
||||||
end, 10)
|
|
||||||
vim.api.nvim_win_set_cursor(0, { 1, 1 })
|
|
||||||
canola.select()
|
|
||||||
test_util.wait_for_autocmd({ 'User', pattern = 'CanolaEnter' })
|
|
||||||
assert.equals('foo', vim.fn.expand('#'))
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('preserves alternate when opening preview', function()
|
|
||||||
vim.cmd.edit({ args = { 'foo' } })
|
|
||||||
canola.open()
|
|
||||||
test_util.wait_for_autocmd({ 'User', pattern = 'CanolaEnter' })
|
|
||||||
assert.equals('foo', vim.fn.expand('#'))
|
|
||||||
vim.wait(1000, function()
|
|
||||||
return canola.get_cursor_entry()
|
|
||||||
end, 10)
|
|
||||||
vim.api.nvim_win_set_cursor(0, { 1, 1 })
|
|
||||||
canola.open_preview()
|
|
||||||
test_util.wait_for_autocmd({ 'User', pattern = 'CanolaEnter' })
|
|
||||||
assert.equals('foo', vim.fn.expand('#'))
|
|
||||||
end)
|
|
||||||
|
|
||||||
describe('floating window', function()
|
|
||||||
it('sets previous buffer as alternate', function()
|
|
||||||
vim.cmd.edit({ args = { 'foo' } })
|
|
||||||
canola.open_float()
|
|
||||||
test_util.wait_for_autocmd({ 'User', pattern = 'CanolaEnter' })
|
|
||||||
vim.api.nvim_win_close(0, true)
|
|
||||||
vim.cmd.edit({ args = { 'bar' } })
|
|
||||||
assert.equals('foo', vim.fn.expand('#'))
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('preserves alternate buffer if editing the same file', function()
|
|
||||||
vim.cmd.edit({ args = { 'foo' } })
|
|
||||||
vim.cmd.edit({ args = { 'bar' } })
|
|
||||||
canola.open_float()
|
|
||||||
test_util.wait_for_autocmd({ 'User', pattern = 'CanolaEnter' })
|
|
||||||
vim.api.nvim_win_close(0, true)
|
|
||||||
vim.cmd.edit({ args = { 'bar' } })
|
|
||||||
assert.equals('foo', vim.fn.expand('#'))
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('preserves alternate buffer if discarding changes', function()
|
|
||||||
vim.cmd.edit({ args = { 'foo' } })
|
|
||||||
vim.cmd.edit({ args = { 'bar' } })
|
|
||||||
canola.open_float()
|
|
||||||
test_util.wait_for_autocmd({ 'User', pattern = 'CanolaEnter' })
|
|
||||||
canola.close()
|
|
||||||
assert.equals('foo', vim.fn.expand('#'))
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('preserves alternate when traversing to a new file', function()
|
|
||||||
vim.cmd.edit({ args = { 'foo' } })
|
|
||||||
canola.open_float()
|
|
||||||
test_util.wait_for_autocmd({ 'User', pattern = 'CanolaEnter' })
|
|
||||||
assert.equals('foo', vim.fn.expand('#'))
|
|
||||||
test_util.feedkeys({ '/LICENSE<CR>' }, 10)
|
|
||||||
canola.select()
|
|
||||||
test_util.wait_for_autocmd('BufEnter')
|
|
||||||
assert.equals('LICENSE', vim.fn.expand('%:.'))
|
|
||||||
assert.equals('foo', vim.fn.expand('#'))
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
local canola = require('canola')
|
|
||||||
local test_util = require('spec.test_util')
|
|
||||||
|
|
||||||
describe('close', function()
|
|
||||||
before_each(function()
|
|
||||||
test_util.reset_editor()
|
|
||||||
end)
|
|
||||||
after_each(function()
|
|
||||||
test_util.reset_editor()
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('does not close buffer from visual mode', function()
|
|
||||||
canola.open()
|
|
||||||
test_util.wait_for_autocmd({ 'User', pattern = 'CanolaEnter' })
|
|
||||||
assert.equals('canola', vim.bo.filetype)
|
|
||||||
test_util.feedkeys({ 'V' }, 10)
|
|
||||||
canola.close()
|
|
||||||
assert.equals('canola', vim.bo.filetype)
|
|
||||||
test_util.feedkeys({ '<Esc>' }, 10)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('does not close buffer from operator-pending mode', function()
|
|
||||||
canola.open()
|
|
||||||
test_util.wait_for_autocmd({ 'User', pattern = 'CanolaEnter' })
|
|
||||||
assert.equals('canola', vim.bo.filetype)
|
|
||||||
vim.api.nvim_feedkeys('d', 'n', false)
|
|
||||||
vim.wait(20)
|
|
||||||
local mode = vim.api.nvim_get_mode().mode
|
|
||||||
if mode:match('^no') then
|
|
||||||
canola.close()
|
|
||||||
assert.equals('canola', vim.bo.filetype)
|
|
||||||
end
|
|
||||||
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes('<Esc>', true, true, true), 'n', false)
|
|
||||||
vim.wait(20)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('closes buffer from normal mode', function()
|
|
||||||
canola.open()
|
|
||||||
test_util.wait_for_autocmd({ 'User', pattern = 'CanolaEnter' })
|
|
||||||
assert.equals('canola', vim.bo.filetype)
|
|
||||||
canola.close()
|
|
||||||
assert.not_equals('canola', vim.bo.filetype)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
local config = require('canola.config')
|
|
||||||
|
|
||||||
describe('config', function()
|
|
||||||
after_each(function()
|
|
||||||
vim.g.canola = nil
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('falls back to vim.g.canola when setup() is called with no args', function()
|
|
||||||
vim.g.canola = { delete_to_trash = true, cleanup_delay_ms = 5000 }
|
|
||||||
config.setup()
|
|
||||||
assert.is_true(config.delete_to_trash)
|
|
||||||
assert.equals(5000, config.cleanup_delay_ms)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('uses defaults when neither opts nor vim.g.canola is set', function()
|
|
||||||
vim.g.canola = nil
|
|
||||||
config.setup()
|
|
||||||
assert.is_false(config.delete_to_trash)
|
|
||||||
assert.equals(2000, config.cleanup_delay_ms)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('prefers explicit opts over vim.g.canola', function()
|
|
||||||
vim.g.canola = { delete_to_trash = true }
|
|
||||||
config.setup({ delete_to_trash = false })
|
|
||||||
assert.is_false(config.delete_to_trash)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
@ -1,209 +0,0 @@
|
||||||
local TmpDir = require('spec.tmpdir')
|
|
||||||
local files = require('canola.adapters.files')
|
|
||||||
local test_util = require('spec.test_util')
|
|
||||||
|
|
||||||
describe('files adapter', function()
|
|
||||||
local tmpdir
|
|
||||||
before_each(function()
|
|
||||||
tmpdir = TmpDir.new()
|
|
||||||
end)
|
|
||||||
after_each(function()
|
|
||||||
if tmpdir then
|
|
||||||
tmpdir:dispose()
|
|
||||||
end
|
|
||||||
test_util.reset_editor()
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('tmpdir creates files and asserts they exist', function()
|
|
||||||
tmpdir:create({ 'a.txt', 'foo/b.txt', 'foo/c.txt', 'bar/' })
|
|
||||||
tmpdir:assert_fs({
|
|
||||||
['a.txt'] = 'a.txt',
|
|
||||||
['foo/b.txt'] = 'foo/b.txt',
|
|
||||||
['foo/c.txt'] = 'foo/c.txt',
|
|
||||||
['bar/'] = true,
|
|
||||||
})
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('Creates files', function()
|
|
||||||
local err = test_util.await(files.perform_action, 2, {
|
|
||||||
url = 'canola://' .. vim.fn.fnamemodify(tmpdir.path, ':p') .. 'a.txt',
|
|
||||||
entry_type = 'file',
|
|
||||||
type = 'create',
|
|
||||||
})
|
|
||||||
assert.is_nil(err)
|
|
||||||
tmpdir:assert_fs({
|
|
||||||
['a.txt'] = '',
|
|
||||||
})
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('Creates directories', function()
|
|
||||||
local err = test_util.await(files.perform_action, 2, {
|
|
||||||
url = 'canola://' .. vim.fn.fnamemodify(tmpdir.path, ':p') .. 'a',
|
|
||||||
entry_type = 'directory',
|
|
||||||
type = 'create',
|
|
||||||
})
|
|
||||||
assert.is_nil(err)
|
|
||||||
tmpdir:assert_fs({
|
|
||||||
['a/'] = true,
|
|
||||||
})
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('Deletes files', function()
|
|
||||||
tmpdir:create({ 'a.txt' })
|
|
||||||
local url = 'canola://' .. vim.fn.fnamemodify(tmpdir.path, ':p') .. 'a.txt'
|
|
||||||
local err = test_util.await(files.perform_action, 2, {
|
|
||||||
url = url,
|
|
||||||
entry_type = 'file',
|
|
||||||
type = 'delete',
|
|
||||||
})
|
|
||||||
assert.is_nil(err)
|
|
||||||
tmpdir:assert_fs({})
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('Deletes directories', function()
|
|
||||||
tmpdir:create({ 'a/' })
|
|
||||||
local url = 'canola://' .. vim.fn.fnamemodify(tmpdir.path, ':p') .. 'a'
|
|
||||||
local err = test_util.await(files.perform_action, 2, {
|
|
||||||
url = url,
|
|
||||||
entry_type = 'directory',
|
|
||||||
type = 'delete',
|
|
||||||
})
|
|
||||||
assert.is_nil(err)
|
|
||||||
tmpdir:assert_fs({})
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('Moves files', function()
|
|
||||||
tmpdir:create({ 'a.txt' })
|
|
||||||
local src_url = 'canola://' .. vim.fn.fnamemodify(tmpdir.path, ':p') .. 'a.txt'
|
|
||||||
local dest_url = 'canola://' .. vim.fn.fnamemodify(tmpdir.path, ':p') .. 'b.txt'
|
|
||||||
local err = test_util.await(files.perform_action, 2, {
|
|
||||||
src_url = src_url,
|
|
||||||
dest_url = dest_url,
|
|
||||||
entry_type = 'file',
|
|
||||||
type = 'move',
|
|
||||||
})
|
|
||||||
assert.is_nil(err)
|
|
||||||
tmpdir:assert_fs({
|
|
||||||
['b.txt'] = 'a.txt',
|
|
||||||
})
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('Moves directories', function()
|
|
||||||
tmpdir:create({ 'a/a.txt' })
|
|
||||||
local src_url = 'canola://' .. vim.fn.fnamemodify(tmpdir.path, ':p') .. 'a'
|
|
||||||
local dest_url = 'canola://' .. vim.fn.fnamemodify(tmpdir.path, ':p') .. 'b'
|
|
||||||
local err = test_util.await(files.perform_action, 2, {
|
|
||||||
src_url = src_url,
|
|
||||||
dest_url = dest_url,
|
|
||||||
entry_type = 'directory',
|
|
||||||
type = 'move',
|
|
||||||
})
|
|
||||||
assert.is_nil(err)
|
|
||||||
tmpdir:assert_fs({
|
|
||||||
['b/a.txt'] = 'a/a.txt',
|
|
||||||
['b/'] = true,
|
|
||||||
})
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('Copies files', function()
|
|
||||||
tmpdir:create({ 'a.txt' })
|
|
||||||
local src_url = 'canola://' .. vim.fn.fnamemodify(tmpdir.path, ':p') .. 'a.txt'
|
|
||||||
local dest_url = 'canola://' .. vim.fn.fnamemodify(tmpdir.path, ':p') .. 'b.txt'
|
|
||||||
local err = test_util.await(files.perform_action, 2, {
|
|
||||||
src_url = src_url,
|
|
||||||
dest_url = dest_url,
|
|
||||||
entry_type = 'file',
|
|
||||||
type = 'copy',
|
|
||||||
})
|
|
||||||
assert.is_nil(err)
|
|
||||||
tmpdir:assert_fs({
|
|
||||||
['a.txt'] = 'a.txt',
|
|
||||||
['b.txt'] = 'a.txt',
|
|
||||||
})
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('Recursively copies directories', function()
|
|
||||||
tmpdir:create({ 'a/a.txt' })
|
|
||||||
local src_url = 'canola://' .. vim.fn.fnamemodify(tmpdir.path, ':p') .. 'a'
|
|
||||||
local dest_url = 'canola://' .. vim.fn.fnamemodify(tmpdir.path, ':p') .. 'b'
|
|
||||||
local err = test_util.await(files.perform_action, 2, {
|
|
||||||
src_url = src_url,
|
|
||||||
dest_url = dest_url,
|
|
||||||
entry_type = 'directory',
|
|
||||||
type = 'copy',
|
|
||||||
})
|
|
||||||
assert.is_nil(err)
|
|
||||||
tmpdir:assert_fs({
|
|
||||||
['b/a.txt'] = 'a/a.txt',
|
|
||||||
['b/'] = true,
|
|
||||||
['a/a.txt'] = 'a/a.txt',
|
|
||||||
['a/'] = true,
|
|
||||||
})
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('Editing a new canola://path/ creates an canola buffer', function()
|
|
||||||
local tmpdir_url = 'canola://' .. vim.fn.fnamemodify(tmpdir.path, ':p') .. '/'
|
|
||||||
vim.cmd.edit({ args = { tmpdir_url } })
|
|
||||||
test_util.wait_canola_ready()
|
|
||||||
local new_url = 'canola://' .. vim.fn.fnamemodify(tmpdir.path, ':p') .. 'newdir'
|
|
||||||
vim.cmd.edit({ args = { new_url } })
|
|
||||||
test_util.wait_canola_ready()
|
|
||||||
assert.equals('canola', vim.bo.filetype)
|
|
||||||
assert.equals(new_url .. '/', vim.api.nvim_buf_get_name(0))
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('Editing a new canola://file.rb creates a normal buffer', function()
|
|
||||||
local tmpdir_url = 'canola://' .. vim.fn.fnamemodify(tmpdir.path, ':p') .. '/'
|
|
||||||
vim.cmd.edit({ args = { tmpdir_url } })
|
|
||||||
test_util.wait_for_autocmd('BufReadPost')
|
|
||||||
local new_url = 'canola://' .. vim.fn.fnamemodify(tmpdir.path, ':p') .. 'file.rb'
|
|
||||||
vim.cmd.edit({ args = { new_url } })
|
|
||||||
test_util.wait_for_autocmd('BufReadPost')
|
|
||||||
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(tmpdir.path .. '/file.rb', vim.fn.bufname())
|
|
||||||
end)
|
|
||||||
|
|
||||||
describe('cleanup_buffers_on_delete', function()
|
|
||||||
local cache = require('canola.cache')
|
|
||||||
local config = require('canola.config')
|
|
||||||
local mutator = require('canola.mutator')
|
|
||||||
|
|
||||||
before_each(function()
|
|
||||||
config.cleanup_buffers_on_delete = true
|
|
||||||
end)
|
|
||||||
|
|
||||||
after_each(function()
|
|
||||||
config.cleanup_buffers_on_delete = false
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('wipes the buffer for a deleted file', function()
|
|
||||||
tmpdir:create({ 'a.txt' })
|
|
||||||
local dirurl = 'canola://' .. vim.fn.fnamemodify(tmpdir.path, ':p')
|
|
||||||
local filepath = vim.fn.fnamemodify(tmpdir.path, ':p') .. 'a.txt'
|
|
||||||
cache.create_and_store_entry(dirurl, 'a.txt', 'file')
|
|
||||||
vim.cmd.edit({ args = { filepath } })
|
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
|
||||||
local url = 'canola://' .. filepath
|
|
||||||
test_util.await(mutator.process_actions, 2, {
|
|
||||||
{ type = 'delete', url = url, entry_type = 'file' },
|
|
||||||
})
|
|
||||||
assert.is_false(vim.api.nvim_buf_is_valid(bufnr))
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('does not wipe the buffer when disabled', function()
|
|
||||||
config.cleanup_buffers_on_delete = false
|
|
||||||
tmpdir:create({ 'b.txt' })
|
|
||||||
local dirurl = 'canola://' .. vim.fn.fnamemodify(tmpdir.path, ':p')
|
|
||||||
local filepath = vim.fn.fnamemodify(tmpdir.path, ':p') .. 'b.txt'
|
|
||||||
cache.create_and_store_entry(dirurl, 'b.txt', 'file')
|
|
||||||
vim.cmd.edit({ args = { filepath } })
|
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
|
||||||
local url = 'canola://' .. filepath
|
|
||||||
test_util.await(mutator.process_actions, 2, {
|
|
||||||
{ type = 'delete', url = url, entry_type = 'file' },
|
|
||||||
})
|
|
||||||
assert.is_true(vim.api.nvim_buf_is_valid(bufnr))
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
vim.cmd([[set runtimepath=$VIMRUNTIME]])
|
|
||||||
vim.opt.runtimepath:append('.')
|
|
||||||
vim.opt.packpath = {}
|
|
||||||
vim.o.swapfile = false
|
|
||||||
vim.cmd('filetype on')
|
|
||||||
vim.fn.mkdir(vim.fn.stdpath('cache'), 'p')
|
|
||||||
vim.fn.mkdir(vim.fn.stdpath('data'), 'p')
|
|
||||||
vim.fn.mkdir(vim.fn.stdpath('state'), 'p')
|
|
||||||
require('spec.test_util').reset_editor()
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
local fs = require('canola.fs')
|
|
||||||
local test_util = require('spec.test_util')
|
|
||||||
local util = require('canola.util')
|
|
||||||
|
|
||||||
describe('update_moved_buffers', function()
|
|
||||||
after_each(function()
|
|
||||||
test_util.reset_editor()
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('Renames moved buffers', function()
|
|
||||||
vim.cmd.edit({ args = { 'canola-test:///foo/bar.txt' } })
|
|
||||||
util.update_moved_buffers('file', 'canola-test:///foo/bar.txt', 'canola-test:///foo/baz.txt')
|
|
||||||
assert.equals('canola-test:///foo/baz.txt', vim.api.nvim_buf_get_name(0))
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('Renames moved buffers when they are normal files', function()
|
|
||||||
local tmpdir = fs.join(vim.loop.fs_realpath(vim.fn.stdpath('cache')), 'canola', 'test')
|
|
||||||
local testfile = fs.join(tmpdir, 'foo.txt')
|
|
||||||
vim.cmd.edit({ args = { testfile } })
|
|
||||||
util.update_moved_buffers(
|
|
||||||
'file',
|
|
||||||
'canola://' .. fs.os_to_posix_path(testfile),
|
|
||||||
'canola://' .. fs.os_to_posix_path(fs.join(tmpdir, 'bar.txt'))
|
|
||||||
)
|
|
||||||
assert.equals(fs.join(tmpdir, 'bar.txt'), vim.api.nvim_buf_get_name(0))
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('Renames directories', function()
|
|
||||||
vim.cmd.edit({ args = { 'canola-test:///foo/' } })
|
|
||||||
util.update_moved_buffers('directory', 'canola-test:///foo/', 'canola-test:///bar/')
|
|
||||||
assert.equals('canola-test:///bar/', vim.api.nvim_buf_get_name(0))
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('Renames subdirectories', function()
|
|
||||||
vim.cmd.edit({ args = { 'canola-test:///foo/bar/' } })
|
|
||||||
util.update_moved_buffers('directory', 'canola-test:///foo/', 'canola-test:///baz/')
|
|
||||||
assert.equals('canola-test:///baz/bar/', vim.api.nvim_buf_get_name(0))
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('Renames subfiles', function()
|
|
||||||
vim.cmd.edit({ args = { 'canola-test:///foo/bar.txt' } })
|
|
||||||
util.update_moved_buffers('directory', 'canola-test:///foo/', 'canola-test:///baz/')
|
|
||||||
assert.equals('canola-test:///baz/bar.txt', vim.api.nvim_buf_get_name(0))
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('Renames subfiles when they are normal files', function()
|
|
||||||
local tmpdir = fs.join(vim.loop.fs_realpath(vim.fn.stdpath('cache')), 'canola', 'test')
|
|
||||||
local foo = fs.join(tmpdir, 'foo')
|
|
||||||
local bar = fs.join(tmpdir, 'bar')
|
|
||||||
local testfile = fs.join(foo, 'foo.txt')
|
|
||||||
vim.cmd.edit({ args = { testfile } })
|
|
||||||
util.update_moved_buffers(
|
|
||||||
'directory',
|
|
||||||
'canola://' .. fs.os_to_posix_path(foo),
|
|
||||||
'canola://' .. fs.os_to_posix_path(bar)
|
|
||||||
)
|
|
||||||
assert.equals(fs.join(bar, 'foo.txt'), vim.api.nvim_buf_get_name(0))
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
@ -1,419 +0,0 @@
|
||||||
local cache = require('canola.cache')
|
|
||||||
local constants = require('canola.constants')
|
|
||||||
local mutator = require('canola.mutator')
|
|
||||||
local test_adapter = require('canola.adapters.test')
|
|
||||||
local test_util = require('spec.test_util')
|
|
||||||
|
|
||||||
local FIELD_ID = constants.FIELD_ID
|
|
||||||
local FIELD_NAME = constants.FIELD_NAME
|
|
||||||
local FIELD_TYPE = constants.FIELD_TYPE
|
|
||||||
|
|
||||||
describe('mutator', function()
|
|
||||||
after_each(function()
|
|
||||||
test_util.reset_editor()
|
|
||||||
end)
|
|
||||||
|
|
||||||
describe('build actions', function()
|
|
||||||
it('empty diffs produce no actions', function()
|
|
||||||
vim.cmd.edit({ args = { 'canola-test:///foo/' } })
|
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
|
||||||
local actions = mutator.create_actions_from_diffs({
|
|
||||||
[bufnr] = {},
|
|
||||||
})
|
|
||||||
assert.are.same({}, actions)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('constructs CREATE actions', function()
|
|
||||||
vim.cmd.edit({ args = { 'canola-test:///foo/' } })
|
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
|
||||||
local diffs = {
|
|
||||||
{ type = 'new', name = 'a.txt', entry_type = 'file' },
|
|
||||||
}
|
|
||||||
local actions = mutator.create_actions_from_diffs({
|
|
||||||
[bufnr] = diffs,
|
|
||||||
})
|
|
||||||
assert.are.same({
|
|
||||||
{
|
|
||||||
type = 'create',
|
|
||||||
entry_type = 'file',
|
|
||||||
url = 'canola-test:///foo/a.txt',
|
|
||||||
},
|
|
||||||
}, actions)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('constructs DELETE actions', function()
|
|
||||||
local file = test_adapter.test_set('/foo/a.txt', 'file')
|
|
||||||
vim.cmd.edit({ args = { 'canola-test:///foo/' } })
|
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
|
||||||
local diffs = {
|
|
||||||
{ type = 'delete', name = 'a.txt', id = file[FIELD_ID] },
|
|
||||||
}
|
|
||||||
local actions = mutator.create_actions_from_diffs({
|
|
||||||
[bufnr] = diffs,
|
|
||||||
})
|
|
||||||
assert.are.same({
|
|
||||||
{
|
|
||||||
type = 'delete',
|
|
||||||
entry_type = 'file',
|
|
||||||
url = 'canola-test:///foo/a.txt',
|
|
||||||
},
|
|
||||||
}, actions)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('constructs COPY actions', function()
|
|
||||||
local file = test_adapter.test_set('/foo/a.txt', 'file')
|
|
||||||
vim.cmd.edit({ args = { 'canola-test:///foo/' } })
|
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
|
||||||
local diffs = {
|
|
||||||
{ type = 'new', name = 'b.txt', entry_type = 'file', id = file[FIELD_ID] },
|
|
||||||
}
|
|
||||||
local actions = mutator.create_actions_from_diffs({
|
|
||||||
[bufnr] = diffs,
|
|
||||||
})
|
|
||||||
assert.are.same({
|
|
||||||
{
|
|
||||||
type = 'copy',
|
|
||||||
entry_type = 'file',
|
|
||||||
src_url = 'canola-test:///foo/a.txt',
|
|
||||||
dest_url = 'canola-test:///foo/b.txt',
|
|
||||||
},
|
|
||||||
}, actions)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('constructs MOVE actions', function()
|
|
||||||
local file = test_adapter.test_set('/foo/a.txt', 'file')
|
|
||||||
vim.cmd.edit({ args = { 'canola-test:///foo/' } })
|
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
|
||||||
local diffs = {
|
|
||||||
{ type = 'delete', name = 'a.txt', id = file[FIELD_ID] },
|
|
||||||
{ type = 'new', name = 'b.txt', entry_type = 'file', id = file[FIELD_ID] },
|
|
||||||
}
|
|
||||||
local actions = mutator.create_actions_from_diffs({
|
|
||||||
[bufnr] = diffs,
|
|
||||||
})
|
|
||||||
assert.are.same({
|
|
||||||
{
|
|
||||||
type = 'move',
|
|
||||||
entry_type = 'file',
|
|
||||||
src_url = 'canola-test:///foo/a.txt',
|
|
||||||
dest_url = 'canola-test:///foo/b.txt',
|
|
||||||
},
|
|
||||||
}, actions)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('correctly orders MOVE + CREATE', function()
|
|
||||||
local file = test_adapter.test_set('/a.txt', 'file')
|
|
||||||
vim.cmd.edit({ args = { 'canola-test:///' } })
|
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
|
||||||
local diffs = {
|
|
||||||
{ type = 'delete', name = 'a.txt', id = file[FIELD_ID] },
|
|
||||||
{ type = 'new', name = 'b.txt', entry_type = 'file', id = file[FIELD_ID] },
|
|
||||||
{ type = 'new', name = 'a.txt', entry_type = 'file' },
|
|
||||||
}
|
|
||||||
local actions = mutator.create_actions_from_diffs({
|
|
||||||
[bufnr] = diffs,
|
|
||||||
})
|
|
||||||
assert.are.same({
|
|
||||||
{
|
|
||||||
type = 'move',
|
|
||||||
entry_type = 'file',
|
|
||||||
src_url = 'canola-test:///a.txt',
|
|
||||||
dest_url = 'canola-test:///b.txt',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type = 'create',
|
|
||||||
entry_type = 'file',
|
|
||||||
url = 'canola-test:///a.txt',
|
|
||||||
},
|
|
||||||
}, actions)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('resolves MOVE loops', function()
|
|
||||||
local afile = test_adapter.test_set('/a.txt', 'file')
|
|
||||||
local bfile = test_adapter.test_set('/b.txt', 'file')
|
|
||||||
vim.cmd.edit({ args = { 'canola-test:///' } })
|
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
|
||||||
local diffs = {
|
|
||||||
{ type = 'delete', name = 'a.txt', id = afile[FIELD_ID] },
|
|
||||||
{ type = 'new', name = 'b.txt', entry_type = 'file', id = afile[FIELD_ID] },
|
|
||||||
{ type = 'delete', name = 'b.txt', id = bfile[FIELD_ID] },
|
|
||||||
{ type = 'new', name = 'a.txt', entry_type = 'file', id = bfile[FIELD_ID] },
|
|
||||||
}
|
|
||||||
math.randomseed(2983982)
|
|
||||||
local actions = mutator.create_actions_from_diffs({
|
|
||||||
[bufnr] = diffs,
|
|
||||||
})
|
|
||||||
local tmp_url = 'canola-test:///a.txt__canola_tmp_510852'
|
|
||||||
assert.are.same({
|
|
||||||
{
|
|
||||||
type = 'move',
|
|
||||||
entry_type = 'file',
|
|
||||||
src_url = 'canola-test:///a.txt',
|
|
||||||
dest_url = tmp_url,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type = 'move',
|
|
||||||
entry_type = 'file',
|
|
||||||
src_url = 'canola-test:///b.txt',
|
|
||||||
dest_url = 'canola-test:///a.txt',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type = 'move',
|
|
||||||
entry_type = 'file',
|
|
||||||
src_url = tmp_url,
|
|
||||||
dest_url = 'canola-test:///b.txt',
|
|
||||||
},
|
|
||||||
}, actions)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
||||||
describe('order actions', function()
|
|
||||||
it('Creates files inside dir before move', function()
|
|
||||||
local move = {
|
|
||||||
type = 'move',
|
|
||||||
src_url = 'canola-test:///a',
|
|
||||||
dest_url = 'canola-test:///b',
|
|
||||||
entry_type = 'directory',
|
|
||||||
}
|
|
||||||
local create = { type = 'create', url = 'canola-test:///a/hi.txt', entry_type = 'file' }
|
|
||||||
local actions = { move, create }
|
|
||||||
local ordered_actions = mutator.enforce_action_order(actions)
|
|
||||||
assert.are.same({ create, move }, ordered_actions)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('Moves file out of parent before deleting parent', function()
|
|
||||||
local move = {
|
|
||||||
type = 'move',
|
|
||||||
src_url = 'canola-test:///a/b.txt',
|
|
||||||
dest_url = 'canola-test:///b.txt',
|
|
||||||
entry_type = 'file',
|
|
||||||
}
|
|
||||||
local delete = { type = 'delete', url = 'canola-test:///a', entry_type = 'directory' }
|
|
||||||
local actions = { delete, move }
|
|
||||||
local ordered_actions = mutator.enforce_action_order(actions)
|
|
||||||
assert.are.same({ move, delete }, ordered_actions)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('Handles parent child move ordering', function()
|
|
||||||
local move1 = {
|
|
||||||
type = 'move',
|
|
||||||
src_url = 'canola-test:///a/b',
|
|
||||||
dest_url = 'canola-test:///b',
|
|
||||||
entry_type = 'directory',
|
|
||||||
}
|
|
||||||
local move2 = {
|
|
||||||
type = 'move',
|
|
||||||
src_url = 'canola-test:///a',
|
|
||||||
dest_url = 'canola-test:///b/a',
|
|
||||||
entry_type = 'directory',
|
|
||||||
}
|
|
||||||
local actions = { move2, move1 }
|
|
||||||
local ordered_actions = mutator.enforce_action_order(actions)
|
|
||||||
assert.are.same({ move1, move2 }, ordered_actions)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('Handles a delete inside a moved folder', function()
|
|
||||||
local del = {
|
|
||||||
type = 'delete',
|
|
||||||
url = 'canola-test:///a/b.txt',
|
|
||||||
entry_type = 'file',
|
|
||||||
}
|
|
||||||
local move = {
|
|
||||||
type = 'move',
|
|
||||||
src_url = 'canola-test:///a',
|
|
||||||
dest_url = 'canola-test:///b',
|
|
||||||
entry_type = 'directory',
|
|
||||||
}
|
|
||||||
local actions = { move, del }
|
|
||||||
local ordered_actions = mutator.enforce_action_order(actions)
|
|
||||||
assert.are.same({ del, move }, ordered_actions)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('Detects move directory loops', function()
|
|
||||||
local move = {
|
|
||||||
type = 'move',
|
|
||||||
src_url = 'canola-test:///a',
|
|
||||||
dest_url = 'canola-test:///a/b',
|
|
||||||
entry_type = 'directory',
|
|
||||||
}
|
|
||||||
assert.has_error(function()
|
|
||||||
mutator.enforce_action_order({ move })
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('Detects copy directory loops', function()
|
|
||||||
local move = {
|
|
||||||
type = 'copy',
|
|
||||||
src_url = 'canola-test:///a',
|
|
||||||
dest_url = 'canola-test:///a/b',
|
|
||||||
entry_type = 'directory',
|
|
||||||
}
|
|
||||||
assert.has_error(function()
|
|
||||||
mutator.enforce_action_order({ move })
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('Detects nested copy directory loops', function()
|
|
||||||
local move = {
|
|
||||||
type = 'copy',
|
|
||||||
src_url = 'canola-test:///a',
|
|
||||||
dest_url = 'canola-test:///a/b/a',
|
|
||||||
entry_type = 'directory',
|
|
||||||
}
|
|
||||||
assert.has_error(function()
|
|
||||||
mutator.enforce_action_order({ move })
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
||||||
describe('change', function()
|
|
||||||
it('applies CHANGE after CREATE', function()
|
|
||||||
local create = { type = 'create', url = 'canola-test:///a/hi.txt', entry_type = 'file' }
|
|
||||||
local change = {
|
|
||||||
type = 'change',
|
|
||||||
url = 'canola-test:///a/hi.txt',
|
|
||||||
entry_type = 'file',
|
|
||||||
column = 'TEST',
|
|
||||||
value = 'TEST',
|
|
||||||
}
|
|
||||||
local actions = { change, create }
|
|
||||||
local ordered_actions = mutator.enforce_action_order(actions)
|
|
||||||
assert.are.same({ create, change }, ordered_actions)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('applies CHANGE after COPY src', function()
|
|
||||||
local copy = {
|
|
||||||
type = 'copy',
|
|
||||||
src_url = 'canola-test:///a/hi.txt',
|
|
||||||
dest_url = 'canola-test:///b.txt',
|
|
||||||
entry_type = 'file',
|
|
||||||
}
|
|
||||||
local change = {
|
|
||||||
type = 'change',
|
|
||||||
url = 'canola-test:///a/hi.txt',
|
|
||||||
entry_type = 'file',
|
|
||||||
column = 'TEST',
|
|
||||||
value = 'TEST',
|
|
||||||
}
|
|
||||||
local actions = { change, copy }
|
|
||||||
local ordered_actions = mutator.enforce_action_order(actions)
|
|
||||||
assert.are.same({ copy, change }, ordered_actions)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('applies CHANGE after COPY dest', function()
|
|
||||||
local copy = {
|
|
||||||
type = 'copy',
|
|
||||||
src_url = 'canola-test:///b.txt',
|
|
||||||
dest_url = 'canola-test:///a/hi.txt',
|
|
||||||
entry_type = 'file',
|
|
||||||
}
|
|
||||||
local change = {
|
|
||||||
type = 'change',
|
|
||||||
url = 'canola-test:///a/hi.txt',
|
|
||||||
entry_type = 'file',
|
|
||||||
column = 'TEST',
|
|
||||||
value = 'TEST',
|
|
||||||
}
|
|
||||||
local actions = { change, copy }
|
|
||||||
local ordered_actions = mutator.enforce_action_order(actions)
|
|
||||||
assert.are.same({ copy, change }, ordered_actions)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('applies CHANGE after MOVE dest', function()
|
|
||||||
local move = {
|
|
||||||
type = 'move',
|
|
||||||
src_url = 'canola-test:///b.txt',
|
|
||||||
dest_url = 'canola-test:///a/hi.txt',
|
|
||||||
entry_type = 'file',
|
|
||||||
}
|
|
||||||
local change = {
|
|
||||||
type = 'change',
|
|
||||||
url = 'canola-test:///a/hi.txt',
|
|
||||||
entry_type = 'file',
|
|
||||||
column = 'TEST',
|
|
||||||
value = 'TEST',
|
|
||||||
}
|
|
||||||
local actions = { change, move }
|
|
||||||
local ordered_actions = mutator.enforce_action_order(actions)
|
|
||||||
assert.are.same({ move, change }, ordered_actions)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
||||||
describe('perform actions', function()
|
|
||||||
it('creates new entries', function()
|
|
||||||
local actions = {
|
|
||||||
{ type = 'create', url = 'canola-test:///a.txt', entry_type = 'file' },
|
|
||||||
}
|
|
||||||
test_util.await(mutator.process_actions, 2, actions)
|
|
||||||
local files = cache.list_url('canola-test:///')
|
|
||||||
assert.are.same({
|
|
||||||
['a.txt'] = {
|
|
||||||
[FIELD_ID] = 1,
|
|
||||||
[FIELD_TYPE] = 'file',
|
|
||||||
[FIELD_NAME] = 'a.txt',
|
|
||||||
},
|
|
||||||
}, files)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('deletes entries', function()
|
|
||||||
local file = test_adapter.test_set('/a.txt', 'file')
|
|
||||||
local actions = {
|
|
||||||
{ type = 'delete', url = 'canola-test:///a.txt', entry_type = 'file' },
|
|
||||||
}
|
|
||||||
test_util.await(mutator.process_actions, 2, actions)
|
|
||||||
local files = cache.list_url('canola-test:///')
|
|
||||||
assert.are.same({}, files)
|
|
||||||
assert.is_nil(cache.get_entry_by_id(file[FIELD_ID]))
|
|
||||||
assert.has_error(function()
|
|
||||||
cache.get_parent_url(file[FIELD_ID])
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('moves entries', function()
|
|
||||||
local file = test_adapter.test_set('/a.txt', 'file')
|
|
||||||
local actions = {
|
|
||||||
{
|
|
||||||
type = 'move',
|
|
||||||
src_url = 'canola-test:///a.txt',
|
|
||||||
dest_url = 'canola-test:///b.txt',
|
|
||||||
entry_type = 'file',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
test_util.await(mutator.process_actions, 2, actions)
|
|
||||||
local files = cache.list_url('canola-test:///')
|
|
||||||
local new_entry = {
|
|
||||||
[FIELD_ID] = file[FIELD_ID],
|
|
||||||
[FIELD_TYPE] = 'file',
|
|
||||||
[FIELD_NAME] = 'b.txt',
|
|
||||||
}
|
|
||||||
assert.are.same({
|
|
||||||
['b.txt'] = new_entry,
|
|
||||||
}, files)
|
|
||||||
assert.are.same(new_entry, cache.get_entry_by_id(file[FIELD_ID]))
|
|
||||||
assert.equals('canola-test:///', cache.get_parent_url(file[FIELD_ID]))
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('copies entries', function()
|
|
||||||
local file = test_adapter.test_set('/a.txt', 'file')
|
|
||||||
local actions = {
|
|
||||||
{
|
|
||||||
type = 'copy',
|
|
||||||
src_url = 'canola-test:///a.txt',
|
|
||||||
dest_url = 'canola-test:///b.txt',
|
|
||||||
entry_type = 'file',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
test_util.await(mutator.process_actions, 2, actions)
|
|
||||||
local files = cache.list_url('canola-test:///')
|
|
||||||
local new_entry = {
|
|
||||||
[FIELD_ID] = file[FIELD_ID] + 1,
|
|
||||||
[FIELD_TYPE] = 'file',
|
|
||||||
[FIELD_NAME] = 'b.txt',
|
|
||||||
}
|
|
||||||
assert.are.same({
|
|
||||||
['a.txt'] = file,
|
|
||||||
['b.txt'] = new_entry,
|
|
||||||
}, files)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
@ -1,249 +0,0 @@
|
||||||
local constants = require('canola.constants')
|
|
||||||
local parser = require('canola.mutator.parser')
|
|
||||||
local test_adapter = require('canola.adapters.test')
|
|
||||||
local test_util = require('spec.test_util')
|
|
||||||
local util = require('canola.util')
|
|
||||||
local view = require('canola.view')
|
|
||||||
|
|
||||||
local FIELD_ID = constants.FIELD_ID
|
|
||||||
local FIELD_META = constants.FIELD_META
|
|
||||||
|
|
||||||
local function set_lines(bufnr, lines)
|
|
||||||
vim.bo[bufnr].modifiable = true
|
|
||||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, lines)
|
|
||||||
end
|
|
||||||
|
|
||||||
describe('parser', function()
|
|
||||||
after_each(function()
|
|
||||||
test_util.reset_editor()
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('detects new files', function()
|
|
||||||
vim.cmd.edit({ args = { 'canola-test:///foo/' } })
|
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
|
||||||
set_lines(bufnr, {
|
|
||||||
'a.txt',
|
|
||||||
})
|
|
||||||
local diffs = parser.parse(bufnr)
|
|
||||||
assert.are.same({ { entry_type = 'file', name = 'a.txt', type = 'new' } }, diffs)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('detects new directories', function()
|
|
||||||
vim.cmd.edit({ args = { 'canola-test:///foo/' } })
|
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
|
||||||
set_lines(bufnr, {
|
|
||||||
'foo/',
|
|
||||||
})
|
|
||||||
local diffs = parser.parse(bufnr)
|
|
||||||
assert.are.same({ { entry_type = 'directory', name = 'foo', type = 'new' } }, diffs)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('detects new links', function()
|
|
||||||
vim.cmd.edit({ args = { 'canola-test:///foo/' } })
|
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
|
||||||
set_lines(bufnr, {
|
|
||||||
'a.txt -> b.txt',
|
|
||||||
})
|
|
||||||
local diffs = parser.parse(bufnr)
|
|
||||||
assert.are.same(
|
|
||||||
{ { entry_type = 'link', name = 'a.txt', type = 'new', link = 'b.txt' } },
|
|
||||||
diffs
|
|
||||||
)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('detects deleted files', function()
|
|
||||||
local file = test_adapter.test_set('/foo/a.txt', 'file')
|
|
||||||
vim.cmd.edit({ args = { 'canola-test:///foo/' } })
|
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
|
||||||
set_lines(bufnr, {})
|
|
||||||
local diffs = parser.parse(bufnr)
|
|
||||||
assert.are.same({
|
|
||||||
{ name = 'a.txt', type = 'delete', id = file[FIELD_ID] },
|
|
||||||
}, diffs)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('detects deleted directories', function()
|
|
||||||
local dir = test_adapter.test_set('/foo/bar', 'directory')
|
|
||||||
vim.cmd.edit({ args = { 'canola-test:///foo/' } })
|
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
|
||||||
set_lines(bufnr, {})
|
|
||||||
local diffs = parser.parse(bufnr)
|
|
||||||
assert.are.same({
|
|
||||||
{ name = 'bar', type = 'delete', id = dir[FIELD_ID] },
|
|
||||||
}, diffs)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('detects deleted links', function()
|
|
||||||
local file = test_adapter.test_set('/foo/a.txt', 'link')
|
|
||||||
file[FIELD_META] = { link = 'b.txt' }
|
|
||||||
vim.cmd.edit({ args = { 'canola-test:///foo/' } })
|
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
|
||||||
set_lines(bufnr, {})
|
|
||||||
local diffs = parser.parse(bufnr)
|
|
||||||
assert.are.same({
|
|
||||||
{ name = 'a.txt', type = 'delete', id = file[FIELD_ID] },
|
|
||||||
}, diffs)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('ignores empty lines', function()
|
|
||||||
local file = test_adapter.test_set('/foo/a.txt', 'file')
|
|
||||||
vim.cmd.edit({ args = { 'canola-test:///foo/' } })
|
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
|
||||||
local cols = view.format_entry_cols(file, {}, {}, test_adapter, false)
|
|
||||||
local lines = util.render_table({ cols }, {})
|
|
||||||
table.insert(lines, '')
|
|
||||||
table.insert(lines, ' ')
|
|
||||||
set_lines(bufnr, lines)
|
|
||||||
local diffs = parser.parse(bufnr)
|
|
||||||
assert.are.same({}, diffs)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('errors on missing filename', function()
|
|
||||||
vim.cmd.edit({ args = { 'canola-test:///foo/' } })
|
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
|
||||||
set_lines(bufnr, {
|
|
||||||
'/008',
|
|
||||||
})
|
|
||||||
local _, errors = parser.parse(bufnr)
|
|
||||||
assert.are_same({
|
|
||||||
{
|
|
||||||
message = 'Malformed ID at start of line',
|
|
||||||
lnum = 0,
|
|
||||||
end_lnum = 1,
|
|
||||||
col = 0,
|
|
||||||
},
|
|
||||||
}, errors)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('errors on empty dirname', function()
|
|
||||||
vim.cmd.edit({ args = { 'canola-test:///foo/' } })
|
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
|
||||||
set_lines(bufnr, {
|
|
||||||
'/008 /',
|
|
||||||
})
|
|
||||||
local _, errors = parser.parse(bufnr)
|
|
||||||
assert.are.same({
|
|
||||||
{
|
|
||||||
message = 'No filename found',
|
|
||||||
lnum = 0,
|
|
||||||
end_lnum = 1,
|
|
||||||
col = 0,
|
|
||||||
},
|
|
||||||
}, errors)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('errors on duplicate names', function()
|
|
||||||
vim.cmd.edit({ args = { 'canola-test:///foo/' } })
|
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
|
||||||
set_lines(bufnr, {
|
|
||||||
'foo',
|
|
||||||
'foo/',
|
|
||||||
})
|
|
||||||
local _, errors = parser.parse(bufnr)
|
|
||||||
assert.are.same({
|
|
||||||
{
|
|
||||||
message = 'Duplicate filename',
|
|
||||||
lnum = 1,
|
|
||||||
end_lnum = 2,
|
|
||||||
col = 0,
|
|
||||||
},
|
|
||||||
}, errors)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('errors on duplicate names for existing files', function()
|
|
||||||
local file = test_adapter.test_set('/foo/a.txt', 'file')
|
|
||||||
vim.cmd.edit({ args = { 'canola-test:///foo/' } })
|
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
|
||||||
set_lines(bufnr, {
|
|
||||||
'a.txt',
|
|
||||||
string.format('/%d a.txt', file[FIELD_ID]),
|
|
||||||
})
|
|
||||||
local _, errors = parser.parse(bufnr)
|
|
||||||
assert.are.same({
|
|
||||||
{
|
|
||||||
message = 'Duplicate filename',
|
|
||||||
lnum = 1,
|
|
||||||
end_lnum = 2,
|
|
||||||
col = 0,
|
|
||||||
},
|
|
||||||
}, errors)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('ignores new dirs with empty name', function()
|
|
||||||
vim.cmd.edit({ args = { 'canola-test:///foo/' } })
|
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
|
||||||
set_lines(bufnr, {
|
|
||||||
'/',
|
|
||||||
})
|
|
||||||
local diffs = parser.parse(bufnr)
|
|
||||||
assert.are.same({}, diffs)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('parses a rename as a delete + new', function()
|
|
||||||
local file = test_adapter.test_set('/foo/a.txt', 'file')
|
|
||||||
vim.cmd.edit({ args = { 'canola-test:///foo/' } })
|
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
|
||||||
set_lines(bufnr, {
|
|
||||||
string.format('/%d b.txt', file[FIELD_ID]),
|
|
||||||
})
|
|
||||||
local diffs = parser.parse(bufnr)
|
|
||||||
assert.are.same({
|
|
||||||
{ type = 'new', id = file[FIELD_ID], name = 'b.txt', entry_type = 'file' },
|
|
||||||
{ type = 'delete', id = file[FIELD_ID], name = 'a.txt' },
|
|
||||||
}, diffs)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('detects a new trailing slash as a delete + create', function()
|
|
||||||
local file = test_adapter.test_set('/foo', 'file')
|
|
||||||
vim.cmd.edit({ args = { 'canola-test:///' } })
|
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
|
||||||
set_lines(bufnr, {
|
|
||||||
string.format('/%d foo/', file[FIELD_ID]),
|
|
||||||
})
|
|
||||||
local diffs = parser.parse(bufnr)
|
|
||||||
assert.are.same({
|
|
||||||
{ type = 'new', name = 'foo', entry_type = 'directory' },
|
|
||||||
{ type = 'delete', id = file[FIELD_ID], name = 'foo' },
|
|
||||||
}, diffs)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('detects renamed files that conflict', function()
|
|
||||||
local afile = test_adapter.test_set('/foo/a.txt', 'file')
|
|
||||||
local bfile = test_adapter.test_set('/foo/b.txt', 'file')
|
|
||||||
vim.cmd.edit({ args = { 'canola-test:///foo/' } })
|
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
|
||||||
set_lines(bufnr, {
|
|
||||||
string.format('/%d a.txt', bfile[FIELD_ID]),
|
|
||||||
string.format('/%d b.txt', afile[FIELD_ID]),
|
|
||||||
})
|
|
||||||
local diffs = parser.parse(bufnr)
|
|
||||||
local first_two = { diffs[1], diffs[2] }
|
|
||||||
local last_two = { diffs[3], diffs[4] }
|
|
||||||
table.sort(first_two, function(a, b)
|
|
||||||
return a.id < b.id
|
|
||||||
end)
|
|
||||||
table.sort(last_two, function(a, b)
|
|
||||||
return a.id < b.id
|
|
||||||
end)
|
|
||||||
assert.are.same({
|
|
||||||
{ name = 'b.txt', type = 'new', id = afile[FIELD_ID], entry_type = 'file' },
|
|
||||||
{ name = 'a.txt', type = 'new', id = bfile[FIELD_ID], entry_type = 'file' },
|
|
||||||
}, first_two)
|
|
||||||
assert.are.same({
|
|
||||||
{ name = 'a.txt', type = 'delete', id = afile[FIELD_ID] },
|
|
||||||
{ name = 'b.txt', type = 'delete', id = bfile[FIELD_ID] },
|
|
||||||
}, last_two)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('views link targets with trailing slashes as the same', function()
|
|
||||||
local file = test_adapter.test_set('/foo/mydir', 'link')
|
|
||||||
file[FIELD_META] = { link = 'dir/' }
|
|
||||||
vim.cmd.edit({ args = { 'canola-test:///foo/' } })
|
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
|
||||||
set_lines(bufnr, {
|
|
||||||
string.format('/%d mydir/ -> dir/', file[FIELD_ID]),
|
|
||||||
})
|
|
||||||
local diffs = parser.parse(bufnr)
|
|
||||||
assert.are.same({}, diffs)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
local TmpDir = require('spec.tmpdir')
|
|
||||||
local canola = require('canola')
|
|
||||||
local test_util = require('spec.test_util')
|
|
||||||
local util = require('canola.util')
|
|
||||||
|
|
||||||
describe('canola preview', function()
|
|
||||||
local tmpdir
|
|
||||||
before_each(function()
|
|
||||||
tmpdir = TmpDir.new()
|
|
||||||
end)
|
|
||||||
after_each(function()
|
|
||||||
if tmpdir then
|
|
||||||
tmpdir:dispose()
|
|
||||||
end
|
|
||||||
test_util.reset_editor()
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('opens preview window', function()
|
|
||||||
tmpdir:create({ 'a.txt' })
|
|
||||||
test_util.canola_open(tmpdir.path)
|
|
||||||
test_util.await(canola.open_preview, 2)
|
|
||||||
local preview_win = util.get_preview_win()
|
|
||||||
assert.not_nil(preview_win)
|
|
||||||
assert(preview_win)
|
|
||||||
local bufnr = vim.api.nvim_win_get_buf(preview_win)
|
|
||||||
local preview_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
|
||||||
assert.are.same({ 'a.txt' }, preview_lines)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('opens preview window when open(preview={})', function()
|
|
||||||
tmpdir:create({ 'a.txt' })
|
|
||||||
test_util.canola_open(tmpdir.path, { preview = {} })
|
|
||||||
local preview_win = util.get_preview_win()
|
|
||||||
assert.not_nil(preview_win)
|
|
||||||
assert(preview_win)
|
|
||||||
local bufnr = vim.api.nvim_win_get_buf(preview_win)
|
|
||||||
local preview_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
|
||||||
assert.are.same({ 'a.txt' }, preview_lines)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
@ -1,137 +0,0 @@
|
||||||
local TmpDir = require('spec.tmpdir')
|
|
||||||
local actions = require('canola.actions')
|
|
||||||
local canola = require('canola')
|
|
||||||
local test_util = require('spec.test_util')
|
|
||||||
local view = require('canola.view')
|
|
||||||
|
|
||||||
describe('regression tests', function()
|
|
||||||
local tmpdir
|
|
||||||
before_each(function()
|
|
||||||
tmpdir = TmpDir.new()
|
|
||||||
end)
|
|
||||||
after_each(function()
|
|
||||||
if tmpdir then
|
|
||||||
tmpdir:dispose()
|
|
||||||
tmpdir = nil
|
|
||||||
end
|
|
||||||
test_util.reset_editor()
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('can edit dirs that will be renamed to an existing buffer', function()
|
|
||||||
vim.cmd.edit({ args = { 'README.md' } })
|
|
||||||
vim.cmd.vsplit()
|
|
||||||
vim.cmd.edit({ args = { '%:p:h' } })
|
|
||||||
assert.equals('canola', vim.bo.filetype)
|
|
||||||
vim.cmd.wincmd({ args = { 'p' } })
|
|
||||||
assert.equals('markdown', vim.bo.filetype)
|
|
||||||
vim.cmd.edit({ args = { '%:p:h' } })
|
|
||||||
test_util.wait_for_autocmd({ 'User', pattern = 'CanolaEnter' })
|
|
||||||
assert.equals('canola', vim.bo.filetype)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('places the cursor on correct entry when opening on file', function()
|
|
||||||
vim.cmd.edit({ args = { '.' } })
|
|
||||||
test_util.wait_for_autocmd({ 'User', pattern = 'CanolaEnter' })
|
|
||||||
local entry = canola.get_cursor_entry()
|
|
||||||
assert.not_nil(entry)
|
|
||||||
assert.not_equals('README.md', entry and entry.name)
|
|
||||||
vim.cmd.edit({ args = { 'README.md' } })
|
|
||||||
view.delete_hidden_buffers()
|
|
||||||
canola.open()
|
|
||||||
test_util.wait_for_autocmd({ 'User', pattern = 'CanolaEnter' })
|
|
||||||
entry = canola.get_cursor_entry()
|
|
||||||
assert.equals('README.md', entry and entry.name)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("doesn't close floating windows canola didn't open itself", function()
|
|
||||||
local winid = vim.api.nvim_open_win(vim.fn.bufadd('README.md'), true, {
|
|
||||||
relative = 'editor',
|
|
||||||
row = 1,
|
|
||||||
col = 1,
|
|
||||||
width = 100,
|
|
||||||
height = 100,
|
|
||||||
})
|
|
||||||
canola.open()
|
|
||||||
vim.wait(10)
|
|
||||||
canola.close()
|
|
||||||
vim.wait(10)
|
|
||||||
assert.equals(winid, vim.api.nvim_get_current_win())
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("doesn't close splits on canola.close", function()
|
|
||||||
vim.cmd.edit({ args = { 'README.md' } })
|
|
||||||
vim.cmd.vsplit()
|
|
||||||
local winid = vim.api.nvim_get_current_win()
|
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
|
||||||
canola.open()
|
|
||||||
vim.wait(10)
|
|
||||||
canola.close()
|
|
||||||
vim.wait(10)
|
|
||||||
assert.equals(2, #vim.api.nvim_tabpage_list_wins(0))
|
|
||||||
assert.equals(winid, vim.api.nvim_get_current_win())
|
|
||||||
assert.equals(bufnr, vim.api.nvim_get_current_buf())
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('Returns to empty buffer on close', function()
|
|
||||||
canola.open()
|
|
||||||
test_util.wait_for_autocmd({ 'User', pattern = 'CanolaEnter' })
|
|
||||||
canola.close()
|
|
||||||
assert.not_equals('canola', vim.bo.filetype)
|
|
||||||
assert.equals('', vim.api.nvim_buf_get_name(0))
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('All buffers set nomodified after save', function()
|
|
||||||
tmpdir:create({ 'a.txt' })
|
|
||||||
vim.cmd.edit({ args = { 'canola://' .. vim.fn.fnamemodify(tmpdir.path, ':p') } })
|
|
||||||
local first_dir = vim.api.nvim_get_current_buf()
|
|
||||||
test_util.wait_for_autocmd({ 'User', pattern = 'CanolaEnter' })
|
|
||||||
test_util.feedkeys({ 'dd', 'itest/<esc>', '<CR>' }, 10)
|
|
||||||
vim.wait(1000, function()
|
|
||||||
return vim.bo.modifiable
|
|
||||||
end, 10)
|
|
||||||
test_util.feedkeys({ 'p' }, 10)
|
|
||||||
canola.save({ confirm = false })
|
|
||||||
vim.wait(1000, function()
|
|
||||||
return vim.bo.modifiable
|
|
||||||
end, 10)
|
|
||||||
tmpdir:assert_fs({
|
|
||||||
['test/a.txt'] = 'a.txt',
|
|
||||||
})
|
|
||||||
assert.falsy(vim.bo[first_dir].modified)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("refreshing buffer doesn't lose track of it", function()
|
|
||||||
vim.cmd.edit({ args = { '.' } })
|
|
||||||
test_util.wait_for_autocmd({ 'User', pattern = 'CanolaEnter' })
|
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
|
||||||
vim.cmd.edit({ bang = true })
|
|
||||||
test_util.wait_for_autocmd({ 'User', pattern = 'CanolaEnter' })
|
|
||||||
assert.are.same({ bufnr }, require('canola.view').get_all_buffers())
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('can copy a file multiple times', function()
|
|
||||||
test_util.actions.open({ tmpdir.path })
|
|
||||||
vim.api.nvim_feedkeys('ifoo.txt', 'x', true)
|
|
||||||
test_util.actions.save()
|
|
||||||
vim.api.nvim_feedkeys('yyp$ciWbar.txt', 'x', true)
|
|
||||||
vim.api.nvim_feedkeys('yyp$ciWbaz.txt', 'x', true)
|
|
||||||
test_util.actions.save()
|
|
||||||
assert.are.same({ 'bar.txt', 'baz.txt', 'foo.txt' }, test_util.parse_entries(0))
|
|
||||||
tmpdir:assert_fs({
|
|
||||||
['foo.txt'] = '',
|
|
||||||
['bar.txt'] = '',
|
|
||||||
['baz.txt'] = '',
|
|
||||||
})
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('can open files from floating window', function()
|
|
||||||
tmpdir:create({ 'a.txt' })
|
|
||||||
canola.open_float(tmpdir.path)
|
|
||||||
test_util.wait_for_autocmd({ 'User', pattern = 'CanolaEnter' })
|
|
||||||
actions.select.callback()
|
|
||||||
vim.wait(1000, function()
|
|
||||||
return vim.fn.expand('%:t') == 'a.txt'
|
|
||||||
end, 10)
|
|
||||||
assert.equals('a.txt', vim.fn.expand('%:t'))
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
@ -1,172 +0,0 @@
|
||||||
local cache = require('canola.cache')
|
|
||||||
local test_adapter = require('canola.adapters.test')
|
|
||||||
local util = require('canola.util')
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
M.reset_editor = function()
|
|
||||||
require('canola').setup({
|
|
||||||
columms = {},
|
|
||||||
adapters = {
|
|
||||||
['canola-test://'] = 'test',
|
|
||||||
},
|
|
||||||
prompt_save_on_select_new_entry = false,
|
|
||||||
})
|
|
||||||
vim.cmd.tabonly({ mods = { silent = true } })
|
|
||||||
vim.cmd.new()
|
|
||||||
vim.cmd.only()
|
|
||||||
for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do
|
|
||||||
vim.api.nvim_buf_delete(bufnr, { force = true })
|
|
||||||
end
|
|
||||||
cache.clear_everything()
|
|
||||||
test_adapter.test_clear()
|
|
||||||
end
|
|
||||||
|
|
||||||
local function throwiferr(err, ...)
|
|
||||||
if err then
|
|
||||||
error(err)
|
|
||||||
else
|
|
||||||
return ...
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
M.await = function(fn, nargs, ...)
|
|
||||||
local done = false
|
|
||||||
local results
|
|
||||||
local n_results = 0
|
|
||||||
local args = { ... }
|
|
||||||
args[nargs] = function(...)
|
|
||||||
results = { ... }
|
|
||||||
n_results = select('#', ...)
|
|
||||||
done = true
|
|
||||||
end
|
|
||||||
fn(unpack(args, 1, nargs))
|
|
||||||
vim.wait(10000, function()
|
|
||||||
return done
|
|
||||||
end, 10)
|
|
||||||
if not done then
|
|
||||||
error('M.await timed out')
|
|
||||||
end
|
|
||||||
return unpack(results, 1, n_results)
|
|
||||||
end
|
|
||||||
|
|
||||||
M.await_throwiferr = function(fn, nargs, ...)
|
|
||||||
return throwiferr(M.await(fn, nargs, ...))
|
|
||||||
end
|
|
||||||
|
|
||||||
M.canola_open = function(...)
|
|
||||||
M.await(require('canola').open, 3, ...)
|
|
||||||
end
|
|
||||||
|
|
||||||
M.wait_for_autocmd = function(autocmd)
|
|
||||||
local triggered = false
|
|
||||||
local opts = {
|
|
||||||
pattern = '*',
|
|
||||||
nested = true,
|
|
||||||
once = true,
|
|
||||||
}
|
|
||||||
if type(autocmd) == 'table' then
|
|
||||||
opts = vim.tbl_extend('force', opts, autocmd)
|
|
||||||
autocmd = autocmd[1]
|
|
||||||
opts[1] = nil
|
|
||||||
end
|
|
||||||
opts.callback = vim.schedule_wrap(function()
|
|
||||||
triggered = true
|
|
||||||
end)
|
|
||||||
vim.api.nvim_create_autocmd(autocmd, opts)
|
|
||||||
vim.wait(10000, function()
|
|
||||||
return triggered
|
|
||||||
end, 10)
|
|
||||||
if not triggered then
|
|
||||||
error('wait_for_autocmd timed out waiting for ' .. tostring(autocmd))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
M.wait_canola_ready = function()
|
|
||||||
local ready = false
|
|
||||||
util.run_after_load(
|
|
||||||
0,
|
|
||||||
vim.schedule_wrap(function()
|
|
||||||
ready = true
|
|
||||||
end)
|
|
||||||
)
|
|
||||||
vim.wait(10000, function()
|
|
||||||
return ready
|
|
||||||
end, 10)
|
|
||||||
if not ready then
|
|
||||||
error('wait_canola_ready timed out')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param actions string[]
|
|
||||||
---@param timestep integer
|
|
||||||
M.feedkeys = function(actions, timestep)
|
|
||||||
timestep = timestep or 10
|
|
||||||
vim.wait(timestep)
|
|
||||||
for _, action in ipairs(actions) do
|
|
||||||
vim.wait(timestep)
|
|
||||||
local escaped = vim.api.nvim_replace_termcodes(action, true, false, true)
|
|
||||||
vim.api.nvim_feedkeys(escaped, 'm', true)
|
|
||||||
end
|
|
||||||
vim.wait(timestep)
|
|
||||||
vim.api.nvim_feedkeys('', 'x', true)
|
|
||||||
vim.wait(timestep)
|
|
||||||
end
|
|
||||||
|
|
||||||
M.actions = {
|
|
||||||
---Open canola and wait for it to finish rendering
|
|
||||||
---@param args string[]
|
|
||||||
open = function(args)
|
|
||||||
vim.schedule(function()
|
|
||||||
vim.cmd.Canola({ args = args })
|
|
||||||
if vim.b.canola_ready then
|
|
||||||
vim.api.nvim_exec_autocmds('User', {
|
|
||||||
pattern = 'CanolaEnter',
|
|
||||||
modeline = false,
|
|
||||||
data = { buf = vim.api.nvim_get_current_buf() },
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
M.wait_for_autocmd({ 'User', pattern = 'CanolaEnter' })
|
|
||||||
end,
|
|
||||||
|
|
||||||
---Save all changes and wait for operation to complete
|
|
||||||
save = function()
|
|
||||||
vim.schedule_wrap(require('canola').save)({ confirm = false })
|
|
||||||
M.wait_for_autocmd({ 'User', pattern = 'CanolaMutationComplete' })
|
|
||||||
end,
|
|
||||||
|
|
||||||
---@param bufnr? integer
|
|
||||||
reload = function(bufnr)
|
|
||||||
M.await(require('canola.view').render_buffer_async, 3, bufnr or 0)
|
|
||||||
end,
|
|
||||||
|
|
||||||
---Move cursor to a file or directory in an canola buffer
|
|
||||||
---@param filename string
|
|
||||||
focus = function(filename)
|
|
||||||
local lines = vim.api.nvim_buf_get_lines(0, 0, -1, true)
|
|
||||||
local search = ' ' .. filename .. '$'
|
|
||||||
for i, line in ipairs(lines) do
|
|
||||||
if line:match(search) then
|
|
||||||
vim.api.nvim_win_set_cursor(0, { i, 0 })
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
error('Could not find file ' .. filename)
|
|
||||||
end,
|
|
||||||
}
|
|
||||||
|
|
||||||
---Get the raw list of filenames from an unmodified canola buffer
|
|
||||||
---@param bufnr? integer
|
|
||||||
---@return string[]
|
|
||||||
M.parse_entries = function(bufnr)
|
|
||||||
bufnr = bufnr or 0
|
|
||||||
if vim.bo[bufnr].modified then
|
|
||||||
error("parse_entries doesn't work on a modified canola buffer")
|
|
||||||
end
|
|
||||||
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, true)
|
|
||||||
return vim.tbl_map(function(line)
|
|
||||||
return line:match('^/%d+ +(.+)$')
|
|
||||||
end, lines)
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
|
|
@ -1,149 +0,0 @@
|
||||||
local TmpDir = require('spec.tmpdir')
|
|
||||||
local test_util = require('spec.test_util')
|
|
||||||
|
|
||||||
describe('freedesktop', function()
|
|
||||||
local tmpdir
|
|
||||||
local tmphome
|
|
||||||
local home = vim.env.XDG_DATA_HOME
|
|
||||||
before_each(function()
|
|
||||||
require('canola.config').delete_to_trash = true
|
|
||||||
tmpdir = TmpDir.new()
|
|
||||||
tmphome = TmpDir.new()
|
|
||||||
package.loaded['canola.adapters.trash'] = require('canola.adapters.trash.freedesktop')
|
|
||||||
vim.env.XDG_DATA_HOME = tmphome.path
|
|
||||||
end)
|
|
||||||
after_each(function()
|
|
||||||
vim.env.XDG_DATA_HOME = home
|
|
||||||
if tmpdir then
|
|
||||||
tmpdir:dispose()
|
|
||||||
end
|
|
||||||
if tmphome then
|
|
||||||
tmphome:dispose()
|
|
||||||
end
|
|
||||||
test_util.reset_editor()
|
|
||||||
package.loaded['canola.adapters.trash'] = nil
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('files can be moved to the trash', function()
|
|
||||||
tmpdir:create({ 'a.txt', 'foo/b.txt' })
|
|
||||||
test_util.actions.open({ tmpdir.path })
|
|
||||||
test_util.actions.focus('a.txt')
|
|
||||||
vim.api.nvim_feedkeys('dd', 'x', true)
|
|
||||||
test_util.actions.open({ '--trash', tmpdir.path })
|
|
||||||
vim.api.nvim_feedkeys('p', 'x', true)
|
|
||||||
test_util.actions.save()
|
|
||||||
tmpdir:assert_not_exists('a.txt')
|
|
||||||
tmpdir:assert_exists('foo/b.txt')
|
|
||||||
test_util.actions.reload()
|
|
||||||
assert.are.same({ 'a.txt' }, test_util.parse_entries(0))
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('deleting a file moves it to trash', function()
|
|
||||||
tmpdir:create({ 'a.txt', 'foo/b.txt' })
|
|
||||||
test_util.actions.open({ tmpdir.path })
|
|
||||||
test_util.actions.focus('a.txt')
|
|
||||||
vim.api.nvim_feedkeys('dd', 'x', true)
|
|
||||||
test_util.actions.save()
|
|
||||||
tmpdir:assert_not_exists('a.txt')
|
|
||||||
tmpdir:assert_exists('foo/b.txt')
|
|
||||||
test_util.actions.open({ '--trash', tmpdir.path })
|
|
||||||
assert.are.same({ 'a.txt' }, test_util.parse_entries(0))
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('deleting a directory moves it to trash', function()
|
|
||||||
tmpdir:create({ 'a.txt', 'foo/b.txt' })
|
|
||||||
test_util.actions.open({ tmpdir.path })
|
|
||||||
test_util.actions.focus('foo/')
|
|
||||||
vim.api.nvim_feedkeys('dd', 'x', true)
|
|
||||||
test_util.actions.save()
|
|
||||||
tmpdir:assert_not_exists('foo')
|
|
||||||
tmpdir:assert_exists('a.txt')
|
|
||||||
test_util.actions.open({ '--trash', tmpdir.path })
|
|
||||||
assert.are.same({ 'foo/' }, test_util.parse_entries(0))
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('deleting a file from trash deletes it permanently', function()
|
|
||||||
tmpdir:create({ 'a.txt' })
|
|
||||||
test_util.actions.open({ tmpdir.path })
|
|
||||||
test_util.actions.focus('a.txt')
|
|
||||||
vim.api.nvim_feedkeys('dd', 'x', true)
|
|
||||||
test_util.actions.save()
|
|
||||||
test_util.actions.open({ '--trash', tmpdir.path })
|
|
||||||
test_util.actions.focus('a.txt')
|
|
||||||
vim.api.nvim_feedkeys('dd', 'x', true)
|
|
||||||
test_util.actions.save()
|
|
||||||
test_util.actions.reload()
|
|
||||||
tmpdir:assert_not_exists('a.txt')
|
|
||||||
assert.are.same({}, test_util.parse_entries(0))
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('cannot create files in the trash', function()
|
|
||||||
tmpdir:create({ 'a.txt' })
|
|
||||||
test_util.actions.open({ tmpdir.path })
|
|
||||||
test_util.actions.focus('a.txt')
|
|
||||||
vim.api.nvim_feedkeys('dd', 'x', true)
|
|
||||||
test_util.actions.save()
|
|
||||||
test_util.actions.open({ '--trash', tmpdir.path })
|
|
||||||
vim.api.nvim_feedkeys('onew_file.txt', 'x', true)
|
|
||||||
test_util.actions.save()
|
|
||||||
test_util.actions.reload()
|
|
||||||
assert.are.same({ 'a.txt' }, test_util.parse_entries(0))
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('cannot rename files in the trash', function()
|
|
||||||
tmpdir:create({ 'a.txt' })
|
|
||||||
test_util.actions.open({ tmpdir.path })
|
|
||||||
test_util.actions.focus('a.txt')
|
|
||||||
vim.api.nvim_feedkeys('dd', 'x', true)
|
|
||||||
test_util.actions.save()
|
|
||||||
test_util.actions.open({ '--trash', tmpdir.path })
|
|
||||||
vim.api.nvim_feedkeys('0facwnew_name', 'x', true)
|
|
||||||
test_util.actions.save()
|
|
||||||
test_util.actions.reload()
|
|
||||||
assert.are.same({ 'a.txt' }, test_util.parse_entries(0))
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('cannot copy files in the trash', function()
|
|
||||||
tmpdir:create({ 'a.txt' })
|
|
||||||
test_util.actions.open({ tmpdir.path })
|
|
||||||
test_util.actions.focus('a.txt')
|
|
||||||
vim.api.nvim_feedkeys('dd', 'x', true)
|
|
||||||
test_util.actions.save()
|
|
||||||
test_util.actions.open({ '--trash', tmpdir.path })
|
|
||||||
vim.api.nvim_feedkeys('yypp', 'x', true)
|
|
||||||
test_util.actions.save()
|
|
||||||
test_util.actions.reload()
|
|
||||||
assert.are.same({ 'a.txt' }, test_util.parse_entries(0))
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('can restore files from trash', function()
|
|
||||||
tmpdir:create({ 'a.txt' })
|
|
||||||
test_util.actions.open({ tmpdir.path })
|
|
||||||
test_util.actions.focus('a.txt')
|
|
||||||
vim.api.nvim_feedkeys('dd', 'x', true)
|
|
||||||
test_util.actions.save()
|
|
||||||
test_util.actions.open({ '--trash', tmpdir.path })
|
|
||||||
vim.api.nvim_feedkeys('dd', 'x', true)
|
|
||||||
test_util.actions.open({ tmpdir.path })
|
|
||||||
vim.api.nvim_feedkeys('p', 'x', true)
|
|
||||||
test_util.actions.save()
|
|
||||||
test_util.actions.reload()
|
|
||||||
assert.are.same({ 'a.txt' }, test_util.parse_entries(0))
|
|
||||||
tmpdir:assert_fs({
|
|
||||||
['a.txt'] = 'a.txt',
|
|
||||||
})
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('can have multiple files with the same name in trash', function()
|
|
||||||
tmpdir:create({ 'a.txt' })
|
|
||||||
test_util.actions.open({ tmpdir.path })
|
|
||||||
vim.api.nvim_feedkeys('dd', 'x', true)
|
|
||||||
test_util.actions.save()
|
|
||||||
tmpdir:create({ 'a.txt' })
|
|
||||||
test_util.actions.reload()
|
|
||||||
vim.api.nvim_feedkeys('dd', 'x', true)
|
|
||||||
test_util.actions.save()
|
|
||||||
test_util.actions.open({ '--trash', tmpdir.path })
|
|
||||||
assert.are.same({ 'a.txt', 'a.txt' }, test_util.parse_entries(0))
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
local canola = require('canola')
|
|
||||||
local util = require('canola.util')
|
|
||||||
describe('url', function()
|
|
||||||
it('get_url_for_path', function()
|
|
||||||
local cases = {
|
|
||||||
{ '', 'canola://' .. util.addslash(vim.fn.getcwd()) },
|
|
||||||
{
|
|
||||||
'term://~/canola.nvim//52953:/bin/sh',
|
|
||||||
'canola://' .. vim.loop.os_homedir() .. '/canola.nvim/',
|
|
||||||
},
|
|
||||||
{ '/foo/bar.txt', 'canola:///foo/', 'bar.txt' },
|
|
||||||
{ 'canola:///foo/bar.txt', 'canola:///foo/', 'bar.txt' },
|
|
||||||
{ 'canola:///', 'canola:///' },
|
|
||||||
{
|
|
||||||
'canola-ssh://user@hostname:8888//bar.txt',
|
|
||||||
'canola-ssh://user@hostname:8888//',
|
|
||||||
'bar.txt',
|
|
||||||
},
|
|
||||||
{ 'canola-ssh://user@hostname:8888//', 'canola-ssh://user@hostname:8888//' },
|
|
||||||
}
|
|
||||||
for _, case in ipairs(cases) do
|
|
||||||
local input, expected, expected_basename = unpack(case)
|
|
||||||
local output, basename = canola.get_buffer_parent_url(input, true)
|
|
||||||
assert.equals(expected, output, string.format('Parent url for path "%s" failed', input))
|
|
||||||
assert.equals(
|
|
||||||
expected_basename,
|
|
||||||
basename,
|
|
||||||
string.format('Basename for path "%s" failed', input)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
local canola = require('canola')
|
|
||||||
local test_util = require('spec.test_util')
|
|
||||||
|
|
||||||
describe('window options', function()
|
|
||||||
after_each(function()
|
|
||||||
test_util.reset_editor()
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('Restores window options on close', function()
|
|
||||||
vim.cmd.edit({ args = { 'README.md' } })
|
|
||||||
test_util.canola_open()
|
|
||||||
assert.equals('no', vim.o.signcolumn)
|
|
||||||
canola.close()
|
|
||||||
assert.equals('auto', vim.o.signcolumn)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('Restores window options on edit', function()
|
|
||||||
test_util.canola_open()
|
|
||||||
assert.equals('no', vim.o.signcolumn)
|
|
||||||
vim.cmd.edit({ args = { 'README.md' } })
|
|
||||||
assert.equals('auto', vim.o.signcolumn)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('Restores window options on split <filename>', function()
|
|
||||||
test_util.canola_open()
|
|
||||||
assert.equals('no', vim.o.signcolumn)
|
|
||||||
vim.cmd.split({ args = { 'README.md' } })
|
|
||||||
assert.equals('auto', vim.o.signcolumn)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('Restores window options on split', function()
|
|
||||||
test_util.canola_open()
|
|
||||||
assert.equals('no', vim.o.signcolumn)
|
|
||||||
vim.cmd.split()
|
|
||||||
vim.cmd.edit({ args = { 'README.md' } })
|
|
||||||
assert.equals('auto', vim.o.signcolumn)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('Restores window options on tabnew <filename>', function()
|
|
||||||
test_util.canola_open()
|
|
||||||
assert.equals('no', vim.o.signcolumn)
|
|
||||||
vim.cmd.tabnew({ args = { 'README.md' } })
|
|
||||||
assert.equals('auto', vim.o.signcolumn)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('Restores window options on tabnew', function()
|
|
||||||
test_util.canola_open()
|
|
||||||
assert.equals('no', vim.o.signcolumn)
|
|
||||||
vim.cmd.tabnew()
|
|
||||||
vim.cmd.edit({ args = { 'README.md' } })
|
|
||||||
assert.equals('auto', vim.o.signcolumn)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('Sets the window options when re-entering canola buffer', function()
|
|
||||||
canola.open()
|
|
||||||
test_util.wait_for_autocmd({ 'User', pattern = 'CanolaEnter' })
|
|
||||||
assert.truthy(vim.w.canola_did_enter)
|
|
||||||
vim.cmd.edit({ args = { 'README.md' } })
|
|
||||||
assert.falsy(vim.w.canola_did_enter)
|
|
||||||
canola.open()
|
|
||||||
assert.truthy(vim.w.canola_did_enter)
|
|
||||||
vim.cmd.vsplit()
|
|
||||||
assert.truthy(vim.w.canola_did_enter)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
if exists("b:current_syntax")
|
|
||||||
finish
|
|
||||||
endif
|
|
||||||
|
|
||||||
syn match canolaId /^\/\d* / conceal
|
|
||||||
|
|
||||||
let b:current_syntax = "canola"
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
if exists("b:current_syntax")
|
|
||||||
finish
|
|
||||||
endif
|
|
||||||
|
|
||||||
syn match canolaCreate /^CREATE\( BUCKET\)\? /
|
|
||||||
syn match canolaMove /^ MOVE /
|
|
||||||
syn match canolaDelete /^DELETE\( BUCKET\)\? /
|
|
||||||
syn match canolaCopy /^ COPY /
|
|
||||||
syn match canolaChange /^CHANGE /
|
|
||||||
" Trash operations
|
|
||||||
syn match canolaRestore /^RESTORE /
|
|
||||||
syn match canolaPurge /^ PURGE /
|
|
||||||
syn match canolaTrash /^ TRASH /
|
|
||||||
|
|
||||||
let b:current_syntax = "canola_preview"
|
|
||||||
7
syntax/oil.vim
Normal file
7
syntax/oil.vim
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
if exists("b:current_syntax")
|
||||||
|
finish
|
||||||
|
endif
|
||||||
|
|
||||||
|
syn match oilId /^\/\d* / conceal
|
||||||
|
|
||||||
|
let b:current_syntax = "oil"
|
||||||
15
syntax/oil_preview.vim
Normal file
15
syntax/oil_preview.vim
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
if exists("b:current_syntax")
|
||||||
|
finish
|
||||||
|
endif
|
||||||
|
|
||||||
|
syn match oilCreate /^CREATE\( BUCKET\)\? /
|
||||||
|
syn match oilMove /^ MOVE /
|
||||||
|
syn match oilDelete /^DELETE\( BUCKET\)\? /
|
||||||
|
syn match oilCopy /^ COPY /
|
||||||
|
syn match oilChange /^CHANGE /
|
||||||
|
" Trash operations
|
||||||
|
syn match oilRestore /^RESTORE /
|
||||||
|
syn match oilPurge /^ PURGE /
|
||||||
|
syn match oilTrash /^ TRASH /
|
||||||
|
|
||||||
|
let b:current_syntax = "oil_preview"
|
||||||
157
tests/altbuf_spec.lua
Normal file
157
tests/altbuf_spec.lua
Normal file
|
|
@ -0,0 +1,157 @@
|
||||||
|
require("plenary.async").tests.add_to_env()
|
||||||
|
local fs = require("oil.fs")
|
||||||
|
local oil = require("oil")
|
||||||
|
local test_util = require("tests.test_util")
|
||||||
|
|
||||||
|
a.describe("Alternate buffer", function()
|
||||||
|
after_each(function()
|
||||||
|
test_util.reset_editor()
|
||||||
|
end)
|
||||||
|
|
||||||
|
a.it("sets previous buffer as alternate", function()
|
||||||
|
vim.cmd.edit({ args = { "foo" } })
|
||||||
|
oil.open()
|
||||||
|
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
||||||
|
vim.cmd.edit({ args = { "bar" } })
|
||||||
|
assert.equals("foo", vim.fn.expand("#"))
|
||||||
|
end)
|
||||||
|
|
||||||
|
a.it("sets previous buffer as alternate when editing url file", function()
|
||||||
|
vim.cmd.edit({ args = { "foo" } })
|
||||||
|
oil.open()
|
||||||
|
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
||||||
|
local readme = fs.join(vim.fn.getcwd(), "README.md")
|
||||||
|
vim.cmd.edit({ args = { "oil://" .. fs.os_to_posix_path(readme) } })
|
||||||
|
-- We're gonna jump around to 2 different buffers
|
||||||
|
test_util.wait_for_autocmd("BufEnter")
|
||||||
|
test_util.wait_for_autocmd("BufEnter")
|
||||||
|
assert.equals(readme, vim.api.nvim_buf_get_name(0))
|
||||||
|
assert.equals("foo", vim.fn.expand("#"))
|
||||||
|
end)
|
||||||
|
|
||||||
|
a.it("sets previous buffer as alternate when editing oil://", function()
|
||||||
|
vim.cmd.edit({ args = { "foo" } })
|
||||||
|
vim.cmd.edit({ args = { "oil://" .. fs.os_to_posix_path(vim.fn.getcwd()) } })
|
||||||
|
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
||||||
|
vim.cmd.edit({ args = { "bar" } })
|
||||||
|
assert.equals("foo", vim.fn.expand("#"))
|
||||||
|
end)
|
||||||
|
|
||||||
|
a.it("preserves alternate buffer if editing the same file", function()
|
||||||
|
vim.cmd.edit({ args = { "foo" } })
|
||||||
|
vim.cmd.edit({ args = { "bar" } })
|
||||||
|
oil.open()
|
||||||
|
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
||||||
|
vim.cmd.edit({ args = { "bar" } })
|
||||||
|
assert.equals("foo", vim.fn.expand("#"))
|
||||||
|
end)
|
||||||
|
|
||||||
|
a.it("preserves alternate buffer if discarding changes", function()
|
||||||
|
vim.cmd.edit({ args = { "foo" } })
|
||||||
|
vim.cmd.edit({ args = { "bar" } })
|
||||||
|
oil.open()
|
||||||
|
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
||||||
|
oil.close()
|
||||||
|
assert.equals("bar", vim.fn.expand("%"))
|
||||||
|
assert.equals("foo", vim.fn.expand("#"))
|
||||||
|
end)
|
||||||
|
|
||||||
|
a.it("sets previous buffer as alternate after multi-dir hops", function()
|
||||||
|
vim.cmd.edit({ args = { "foo" } })
|
||||||
|
oil.open()
|
||||||
|
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
||||||
|
oil.open()
|
||||||
|
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
||||||
|
oil.open()
|
||||||
|
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
||||||
|
oil.open()
|
||||||
|
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
||||||
|
vim.cmd.edit({ args = { "bar" } })
|
||||||
|
assert.equals("foo", vim.fn.expand("#"))
|
||||||
|
end)
|
||||||
|
|
||||||
|
a.it("sets previous buffer as alternate when inside oil buffer", function()
|
||||||
|
vim.cmd.edit({ args = { "foo" } })
|
||||||
|
oil.open()
|
||||||
|
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
||||||
|
assert.equals("foo", vim.fn.expand("#"))
|
||||||
|
vim.cmd.edit({ args = { "bar" } })
|
||||||
|
assert.equals("foo", vim.fn.expand("#"))
|
||||||
|
oil.open()
|
||||||
|
assert.equals("bar", vim.fn.expand("#"))
|
||||||
|
end)
|
||||||
|
|
||||||
|
a.it("preserves alternate when traversing oil dirs", function()
|
||||||
|
vim.cmd.edit({ args = { "foo" } })
|
||||||
|
oil.open()
|
||||||
|
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
||||||
|
assert.equals("foo", vim.fn.expand("#"))
|
||||||
|
vim.wait(1000, function()
|
||||||
|
return oil.get_cursor_entry()
|
||||||
|
end, 10)
|
||||||
|
vim.api.nvim_win_set_cursor(0, { 1, 1 })
|
||||||
|
oil.select()
|
||||||
|
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
||||||
|
assert.equals("foo", vim.fn.expand("#"))
|
||||||
|
end)
|
||||||
|
|
||||||
|
a.it("preserves alternate when opening preview", function()
|
||||||
|
vim.cmd.edit({ args = { "foo" } })
|
||||||
|
oil.open()
|
||||||
|
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
||||||
|
assert.equals("foo", vim.fn.expand("#"))
|
||||||
|
vim.wait(1000, function()
|
||||||
|
return oil.get_cursor_entry()
|
||||||
|
end, 10)
|
||||||
|
vim.api.nvim_win_set_cursor(0, { 1, 1 })
|
||||||
|
oil.open_preview()
|
||||||
|
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
||||||
|
assert.equals("foo", vim.fn.expand("#"))
|
||||||
|
end)
|
||||||
|
|
||||||
|
a.describe("floating window", function()
|
||||||
|
a.it("sets previous buffer as alternate", function()
|
||||||
|
vim.cmd.edit({ args = { "foo" } })
|
||||||
|
oil.open_float()
|
||||||
|
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
||||||
|
-- This is lazy, but testing the actual select logic is more difficult. We can simply
|
||||||
|
-- replicate it by closing the current window and then doing the edit
|
||||||
|
vim.api.nvim_win_close(0, true)
|
||||||
|
vim.cmd.edit({ args = { "bar" } })
|
||||||
|
assert.equals("foo", vim.fn.expand("#"))
|
||||||
|
end)
|
||||||
|
|
||||||
|
a.it("preserves alternate buffer if editing the same file", function()
|
||||||
|
vim.cmd.edit({ args = { "foo" } })
|
||||||
|
vim.cmd.edit({ args = { "bar" } })
|
||||||
|
oil.open_float()
|
||||||
|
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
||||||
|
-- This is lazy, but testing the actual select logic is more difficult. We can simply
|
||||||
|
-- replicate it by closing the current window and then doing the edit
|
||||||
|
vim.api.nvim_win_close(0, true)
|
||||||
|
vim.cmd.edit({ args = { "bar" } })
|
||||||
|
assert.equals("foo", vim.fn.expand("#"))
|
||||||
|
end)
|
||||||
|
|
||||||
|
a.it("preserves alternate buffer if discarding changes", function()
|
||||||
|
vim.cmd.edit({ args = { "foo" } })
|
||||||
|
vim.cmd.edit({ args = { "bar" } })
|
||||||
|
oil.open_float()
|
||||||
|
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
||||||
|
oil.close()
|
||||||
|
assert.equals("foo", vim.fn.expand("#"))
|
||||||
|
end)
|
||||||
|
|
||||||
|
a.it("preserves alternate when traversing to a new file", function()
|
||||||
|
vim.cmd.edit({ args = { "foo" } })
|
||||||
|
oil.open_float()
|
||||||
|
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
||||||
|
assert.equals("foo", vim.fn.expand("#"))
|
||||||
|
test_util.feedkeys({ "/LICENSE<CR>" }, 10)
|
||||||
|
oil.select()
|
||||||
|
test_util.wait_for_autocmd("BufEnter")
|
||||||
|
assert.equals("LICENSE", vim.fn.expand("%:."))
|
||||||
|
assert.equals("foo", vim.fn.expand("#"))
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
45
tests/close_spec.lua
Normal file
45
tests/close_spec.lua
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
require("plenary.async").tests.add_to_env()
|
||||||
|
local oil = require("oil")
|
||||||
|
local test_util = require("tests.test_util")
|
||||||
|
|
||||||
|
a.describe("close", function()
|
||||||
|
a.before_each(function()
|
||||||
|
test_util.reset_editor()
|
||||||
|
end)
|
||||||
|
a.after_each(function()
|
||||||
|
test_util.reset_editor()
|
||||||
|
end)
|
||||||
|
|
||||||
|
a.it("does not close buffer from visual mode", function()
|
||||||
|
oil.open()
|
||||||
|
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
||||||
|
assert.equals("oil", vim.bo.filetype)
|
||||||
|
test_util.feedkeys({ "V" }, 10)
|
||||||
|
oil.close()
|
||||||
|
assert.equals("oil", vim.bo.filetype)
|
||||||
|
test_util.feedkeys({ "<Esc>" }, 10)
|
||||||
|
end)
|
||||||
|
|
||||||
|
a.it("does not close buffer from operator-pending mode", function()
|
||||||
|
oil.open()
|
||||||
|
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
||||||
|
assert.equals("oil", vim.bo.filetype)
|
||||||
|
vim.api.nvim_feedkeys("d", "n", false)
|
||||||
|
a.util.sleep(20)
|
||||||
|
local mode = vim.api.nvim_get_mode().mode
|
||||||
|
if mode:match("^no") then
|
||||||
|
oil.close()
|
||||||
|
assert.equals("oil", vim.bo.filetype)
|
||||||
|
end
|
||||||
|
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("<Esc>", true, true, true), "n", false)
|
||||||
|
a.util.sleep(20)
|
||||||
|
end)
|
||||||
|
|
||||||
|
a.it("closes buffer from normal mode", function()
|
||||||
|
oil.open()
|
||||||
|
test_util.wait_for_autocmd({ "User", pattern = "OilEnter" })
|
||||||
|
assert.equals("oil", vim.bo.filetype)
|
||||||
|
oil.close()
|
||||||
|
assert.not_equals("oil", vim.bo.filetype)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
27
tests/config_spec.lua
Normal file
27
tests/config_spec.lua
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
local config = require("oil.config")
|
||||||
|
|
||||||
|
describe("config", function()
|
||||||
|
after_each(function()
|
||||||
|
vim.g.oil = nil
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("falls back to vim.g.oil when setup() is called with no args", function()
|
||||||
|
vim.g.oil = { delete_to_trash = true, cleanup_delay_ms = 5000 }
|
||||||
|
config.setup()
|
||||||
|
assert.is_true(config.delete_to_trash)
|
||||||
|
assert.equals(5000, config.cleanup_delay_ms)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("uses defaults when neither opts nor vim.g.oil is set", function()
|
||||||
|
vim.g.oil = nil
|
||||||
|
config.setup()
|
||||||
|
assert.is_false(config.delete_to_trash)
|
||||||
|
assert.equals(2000, config.cleanup_delay_ms)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("prefers explicit opts over vim.g.oil", function()
|
||||||
|
vim.g.oil = { delete_to_trash = true }
|
||||||
|
config.setup({ delete_to_trash = false })
|
||||||
|
assert.is_false(config.delete_to_trash)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
173
tests/files_spec.lua
Normal file
173
tests/files_spec.lua
Normal file
|
|
@ -0,0 +1,173 @@
|
||||||
|
require("plenary.async").tests.add_to_env()
|
||||||
|
local TmpDir = require("tests.tmpdir")
|
||||||
|
local files = require("oil.adapters.files")
|
||||||
|
local test_util = require("tests.test_util")
|
||||||
|
|
||||||
|
a.describe("files adapter", function()
|
||||||
|
local tmpdir
|
||||||
|
a.before_each(function()
|
||||||
|
tmpdir = TmpDir.new()
|
||||||
|
end)
|
||||||
|
a.after_each(function()
|
||||||
|
if tmpdir then
|
||||||
|
tmpdir:dispose()
|
||||||
|
end
|
||||||
|
test_util.reset_editor()
|
||||||
|
end)
|
||||||
|
|
||||||
|
a.it("tmpdir creates files and asserts they exist", function()
|
||||||
|
tmpdir:create({ "a.txt", "foo/b.txt", "foo/c.txt", "bar/" })
|
||||||
|
tmpdir:assert_fs({
|
||||||
|
["a.txt"] = "a.txt",
|
||||||
|
["foo/b.txt"] = "foo/b.txt",
|
||||||
|
["foo/c.txt"] = "foo/c.txt",
|
||||||
|
["bar/"] = true,
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
a.it("Creates files", function()
|
||||||
|
local err = a.wrap(files.perform_action, 2)({
|
||||||
|
url = "oil://" .. vim.fn.fnamemodify(tmpdir.path, ":p") .. "a.txt",
|
||||||
|
entry_type = "file",
|
||||||
|
type = "create",
|
||||||
|
})
|
||||||
|
assert.is_nil(err)
|
||||||
|
tmpdir:assert_fs({
|
||||||
|
["a.txt"] = "",
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
a.it("Creates directories", function()
|
||||||
|
local err = a.wrap(files.perform_action, 2)({
|
||||||
|
url = "oil://" .. vim.fn.fnamemodify(tmpdir.path, ":p") .. "a",
|
||||||
|
entry_type = "directory",
|
||||||
|
type = "create",
|
||||||
|
})
|
||||||
|
assert.is_nil(err)
|
||||||
|
tmpdir:assert_fs({
|
||||||
|
["a/"] = true,
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
a.it("Deletes files", function()
|
||||||
|
tmpdir:create({ "a.txt" })
|
||||||
|
a.util.scheduler()
|
||||||
|
local url = "oil://" .. vim.fn.fnamemodify(tmpdir.path, ":p") .. "a.txt"
|
||||||
|
local err = a.wrap(files.perform_action, 2)({
|
||||||
|
url = url,
|
||||||
|
entry_type = "file",
|
||||||
|
type = "delete",
|
||||||
|
})
|
||||||
|
assert.is_nil(err)
|
||||||
|
tmpdir:assert_fs({})
|
||||||
|
end)
|
||||||
|
|
||||||
|
a.it("Deletes directories", function()
|
||||||
|
tmpdir:create({ "a/" })
|
||||||
|
local url = "oil://" .. vim.fn.fnamemodify(tmpdir.path, ":p") .. "a"
|
||||||
|
local err = a.wrap(files.perform_action, 2)({
|
||||||
|
url = url,
|
||||||
|
entry_type = "directory",
|
||||||
|
type = "delete",
|
||||||
|
})
|
||||||
|
assert.is_nil(err)
|
||||||
|
tmpdir:assert_fs({})
|
||||||
|
end)
|
||||||
|
|
||||||
|
a.it("Moves files", function()
|
||||||
|
tmpdir:create({ "a.txt" })
|
||||||
|
a.util.scheduler()
|
||||||
|
local src_url = "oil://" .. vim.fn.fnamemodify(tmpdir.path, ":p") .. "a.txt"
|
||||||
|
local dest_url = "oil://" .. vim.fn.fnamemodify(tmpdir.path, ":p") .. "b.txt"
|
||||||
|
local err = a.wrap(files.perform_action, 2)({
|
||||||
|
src_url = src_url,
|
||||||
|
dest_url = dest_url,
|
||||||
|
entry_type = "file",
|
||||||
|
type = "move",
|
||||||
|
})
|
||||||
|
assert.is_nil(err)
|
||||||
|
tmpdir:assert_fs({
|
||||||
|
["b.txt"] = "a.txt",
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
a.it("Moves directories", function()
|
||||||
|
tmpdir:create({ "a/a.txt" })
|
||||||
|
a.util.scheduler()
|
||||||
|
local src_url = "oil://" .. vim.fn.fnamemodify(tmpdir.path, ":p") .. "a"
|
||||||
|
local dest_url = "oil://" .. vim.fn.fnamemodify(tmpdir.path, ":p") .. "b"
|
||||||
|
local err = a.wrap(files.perform_action, 2)({
|
||||||
|
src_url = src_url,
|
||||||
|
dest_url = dest_url,
|
||||||
|
entry_type = "directory",
|
||||||
|
type = "move",
|
||||||
|
})
|
||||||
|
assert.is_nil(err)
|
||||||
|
tmpdir:assert_fs({
|
||||||
|
["b/a.txt"] = "a/a.txt",
|
||||||
|
["b/"] = true,
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
a.it("Copies files", function()
|
||||||
|
tmpdir:create({ "a.txt" })
|
||||||
|
a.util.scheduler()
|
||||||
|
local src_url = "oil://" .. vim.fn.fnamemodify(tmpdir.path, ":p") .. "a.txt"
|
||||||
|
local dest_url = "oil://" .. vim.fn.fnamemodify(tmpdir.path, ":p") .. "b.txt"
|
||||||
|
local err = a.wrap(files.perform_action, 2)({
|
||||||
|
src_url = src_url,
|
||||||
|
dest_url = dest_url,
|
||||||
|
entry_type = "file",
|
||||||
|
type = "copy",
|
||||||
|
})
|
||||||
|
assert.is_nil(err)
|
||||||
|
tmpdir:assert_fs({
|
||||||
|
["a.txt"] = "a.txt",
|
||||||
|
["b.txt"] = "a.txt",
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
a.it("Recursively copies directories", function()
|
||||||
|
tmpdir:create({ "a/a.txt" })
|
||||||
|
a.util.scheduler()
|
||||||
|
local src_url = "oil://" .. vim.fn.fnamemodify(tmpdir.path, ":p") .. "a"
|
||||||
|
local dest_url = "oil://" .. vim.fn.fnamemodify(tmpdir.path, ":p") .. "b"
|
||||||
|
local err = a.wrap(files.perform_action, 2)({
|
||||||
|
src_url = src_url,
|
||||||
|
dest_url = dest_url,
|
||||||
|
entry_type = "directory",
|
||||||
|
type = "copy",
|
||||||
|
})
|
||||||
|
assert.is_nil(err)
|
||||||
|
tmpdir:assert_fs({
|
||||||
|
["b/a.txt"] = "a/a.txt",
|
||||||
|
["b/"] = true,
|
||||||
|
["a/a.txt"] = "a/a.txt",
|
||||||
|
["a/"] = true,
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
a.it("Editing a new oil://path/ creates an oil buffer", function()
|
||||||
|
local tmpdir_url = "oil://" .. vim.fn.fnamemodify(tmpdir.path, ":p") .. "/"
|
||||||
|
vim.cmd.edit({ args = { tmpdir_url } })
|
||||||
|
test_util.wait_oil_ready()
|
||||||
|
local new_url = "oil://" .. vim.fn.fnamemodify(tmpdir.path, ":p") .. "newdir"
|
||||||
|
vim.cmd.edit({ args = { new_url } })
|
||||||
|
test_util.wait_oil_ready()
|
||||||
|
assert.equals("oil", vim.bo.filetype)
|
||||||
|
-- The normalization will add a '/'
|
||||||
|
assert.equals(new_url .. "/", vim.api.nvim_buf_get_name(0))
|
||||||
|
end)
|
||||||
|
|
||||||
|
a.it("Editing a new oil://file.rb creates a normal buffer", function()
|
||||||
|
local tmpdir_url = "oil://" .. vim.fn.fnamemodify(tmpdir.path, ":p") .. "/"
|
||||||
|
vim.cmd.edit({ args = { tmpdir_url } })
|
||||||
|
test_util.wait_for_autocmd("BufReadPost")
|
||||||
|
local new_url = "oil://" .. vim.fn.fnamemodify(tmpdir.path, ":p") .. "file.rb"
|
||||||
|
vim.cmd.edit({ args = { new_url } })
|
||||||
|
test_util.wait_for_autocmd("BufReadPost")
|
||||||
|
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(tmpdir.path .. "/file.rb", vim.fn.bufname())
|
||||||
|
end)
|
||||||
|
end)
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue