Skip to content

Commit e8f7a42

Browse files
committed
feat: async rename and create file
1 parent 3c4958a commit e8f7a42

File tree

8 files changed

+373
-67
lines changed

8 files changed

+373
-67
lines changed

doc/nvim-tree-lua.txt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,12 @@ Subsequent calls to setup will replace the previous configuration.
394394
watcher = false,
395395
},
396396
},
397+
experimental = {
398+
async = {
399+
create_file = false,
400+
rename_file = false,
401+
}
402+
}
397403
} -- END_DEFAULT_OPTS
398404
<
399405

@@ -1134,6 +1140,20 @@ Configuration for diagnostic logging.
11341140
|nvim-tree.filesystem_watchers| processing, verbose.
11351141
Type: `boolean`, Default: `false`
11361142

1143+
*nvim-tree.experimental*
1144+
Configuration for experimental features.
1145+
1146+
*nvim-tree.experimental.async*
1147+
Control experimental async behavior.
1148+
1149+
*nvim-tree.experimental.async.create_file*
1150+
Toggle async behavior of create file operation.
1151+
Type: `boolean`, Default: `false`
1152+
1153+
*nvim-tree.experimental.async.rename_file*
1154+
Toggle async behavior of rename file operation.
1155+
Type: `boolean`, Default: `false`
1156+
11371157
==============================================================================
11381158
4.1 VINEGAR STYLE *nvim-tree-vinegar*
11391159

lua/nvim-tree.lua

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,12 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
702702
watcher = false,
703703
},
704704
},
705+
experimental = {
706+
async = {
707+
create_file = false,
708+
rename_file = false,
709+
},
710+
},
705711
} -- END_DEFAULT_OPTS
706712

707713
local function merge_options(conf)

lua/nvim-tree/actions/fs/create-file.lua

Lines changed: 87 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,28 @@ local core = require "nvim-tree.core"
55
local notify = require "nvim-tree.notify"
66

77
local find_file = require("nvim-tree.actions.finders.find-file").fn
8+
local async = require "nvim-tree.async"
89

910
local M = {}
1011

12+
---@async
1113
local function create_and_notify(file)
12-
local ok, fd = pcall(vim.loop.fs_open, file, "w", 420)
13-
if not ok then
14+
local fd, err
15+
if M.enable_async then
16+
err, fd = async.call(vim.loop.fs_open, file, "w", 420)
17+
else
18+
fd, err = vim.loop.fs_open(file, "w", 420)
19+
end
20+
if err then
1421
notify.error("Couldn't create file " .. file)
1522
return
1623
end
17-
vim.loop.fs_close(fd)
18-
events._dispatch_file_created(file)
19-
end
20-
21-
local function create_file(file)
22-
if utils.file_exists(file) then
23-
local prompt_select = "Overwrite " .. file .. " ?"
24-
local prompt_input = prompt_select .. " y/n: "
25-
lib.prompt(prompt_input, prompt_select, { "y", "n" }, { "Yes", "No" }, function(item_short)
26-
utils.clear_prompt()
27-
if item_short == "y" then
28-
create_and_notify(file)
29-
end
30-
end)
24+
if M.enable_async then
25+
async.call(vim.loop.fs_close, fd)
3126
else
32-
create_and_notify(file)
27+
vim.loop.fs_close(fd)
3328
end
29+
events._dispatch_file_created(file)
3430
end
3531

3632
local function get_num_nodes(iter)
@@ -41,6 +37,67 @@ local function get_num_nodes(iter)
4137
return i
4238
end
4339

40+
local function create_file(new_file_path)
41+
-- create a folder for each path element if the folder does not exist
42+
-- if the answer ends with a /, create a file for the last path element
43+
local is_last_path_file = not new_file_path:match(utils.path_separator .. "$")
44+
local path_to_create = ""
45+
local idx = 0
46+
47+
local num_nodes = get_num_nodes(utils.path_split(utils.path_remove_trailing(new_file_path)))
48+
local is_error = false
49+
for path in utils.path_split(new_file_path) do
50+
idx = idx + 1
51+
local p = utils.path_remove_trailing(path)
52+
if #path_to_create == 0 and utils.is_windows then
53+
path_to_create = utils.path_join { p, path_to_create }
54+
else
55+
path_to_create = utils.path_join { path_to_create, p }
56+
end
57+
if is_last_path_file and idx == num_nodes then
58+
if M.enable_async then
59+
async.schedule()
60+
end
61+
if utils.file_exists(new_file_path) then
62+
local prompt_select = "Overwrite " .. new_file_path .. " ?"
63+
local prompt_input = prompt_select .. " y/n: "
64+
if M.enable_async then
65+
local item_short = async.call(lib.prompt, prompt_input, prompt_select, { "y", "n" }, { "Yes", "No" })
66+
utils.clear_prompt()
67+
if item_short == "y" then
68+
create_and_notify(new_file_path)
69+
end
70+
else
71+
lib.prompt(prompt_input, prompt_select, { "y", "n" }, { "Yes", "No" }, function(item_short)
72+
utils.clear_prompt()
73+
if item_short == "y" then
74+
create_and_notify(new_file_path)
75+
end
76+
end)
77+
end
78+
else
79+
create_and_notify(new_file_path)
80+
end
81+
elseif not utils.file_exists(path_to_create) then
82+
local err
83+
if M.enable_async then
84+
err = async.call(vim.loop.fs_mkdir, path_to_create, 493)
85+
else
86+
_, err = vim.loop.fs_mkdir(path_to_create, 493)
87+
end
88+
if err then
89+
notify.error("Could not create folder " .. path_to_create .. ": " .. err)
90+
is_error = true
91+
break
92+
end
93+
events._dispatch_folder_created(new_file_path)
94+
end
95+
end
96+
if not is_error then
97+
notify.info(new_file_path .. " was properly created")
98+
end
99+
end
100+
44101
local function get_containing_folder(node)
45102
if node.nodes ~= nil then
46103
return utils.path_add_trailing(node.absolute_path)
@@ -49,7 +106,8 @@ local function get_containing_folder(node)
49106
return node.absolute_path:sub(0, -node_name_size - 1)
50107
end
51108

52-
function M.fn(node)
109+
--TODO: once async feature is finalized, use `async.wrap` instead of cb param
110+
function M.fn(node, cb)
53111
node = node and lib.get_last_group_node(node)
54112
if not node or node.name == ".." then
55113
node = {
@@ -74,45 +132,24 @@ function M.fn(node)
74132
return
75133
end
76134

77-
-- create a folder for each path element if the folder does not exist
78-
-- if the answer ends with a /, create a file for the last path element
79-
local is_last_path_file = not new_file_path:match(utils.path_separator .. "$")
80-
local path_to_create = ""
81-
local idx = 0
82-
83-
local num_nodes = get_num_nodes(utils.path_split(utils.path_remove_trailing(new_file_path)))
84-
local is_error = false
85-
for path in utils.path_split(new_file_path) do
86-
idx = idx + 1
87-
local p = utils.path_remove_trailing(path)
88-
if #path_to_create == 0 and vim.fn.has "win32" == 1 then
89-
path_to_create = utils.path_join { p, path_to_create }
90-
else
91-
path_to_create = utils.path_join { path_to_create, p }
92-
end
93-
if is_last_path_file and idx == num_nodes then
94-
create_file(path_to_create)
95-
elseif not utils.file_exists(path_to_create) then
96-
local success = vim.loop.fs_mkdir(path_to_create, 493)
97-
if not success then
98-
notify.error("Could not create folder " .. path_to_create)
99-
is_error = true
100-
break
135+
if M.enable_async then
136+
async.exec(create_file, new_file_path, function(err)
137+
find_file(utils.path_remove_trailing(new_file_path))
138+
if cb then
139+
cb(err)
101140
end
102-
events._dispatch_folder_created(new_file_path)
103-
end
104-
end
105-
if not is_error then
106-
notify.info(new_file_path .. " was properly created")
141+
end)
142+
else
143+
create_file(new_file_path)
144+
-- synchronously refreshes as we can't wait for the watchers
145+
find_file(utils.path_remove_trailing(new_file_path))
107146
end
108-
109-
-- synchronously refreshes as we can't wait for the watchers
110-
find_file(utils.path_remove_trailing(new_file_path))
111147
end)
112148
end
113149

114150
function M.setup(opts)
115151
M.enable_reload = not opts.filesystem_watchers.enable
152+
M.enable_async = opts.experimental.async.create_file
116153
end
117154

118155
return M

lua/nvim-tree/actions/fs/rename-file.lua

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ local lib = require "nvim-tree.lib"
22
local utils = require "nvim-tree.utils"
33
local events = require "nvim-tree.events"
44
local notify = require "nvim-tree.notify"
5+
local async = require "nvim-tree.async"
56

67
local M = {}
78

@@ -15,26 +16,34 @@ local function err_fmt(from, to, reason)
1516
return string.format("Cannot rename %s -> %s: %s", from, to, reason)
1617
end
1718

18-
function M.rename(node, to)
19+
---@async
20+
function M.rename(node, to, use_async)
1921
if utils.file_exists(to) then
20-
notify.warn(err_fmt(node.absolute_path, to, "file already exists"))
21-
return
22+
return notify.warn(err_fmt(node.absolute_path, to, "file already exists"))
2223
end
2324

2425
events._dispatch_will_rename_node(node.absolute_path, to)
25-
local success, err = vim.loop.fs_rename(node.absolute_path, to)
26+
local success, err
27+
if use_async then
28+
err, success = async.call(vim.loop.fs_rename, node.absolute_path, to)
29+
else
30+
success, err = vim.loop.fs_rename(node.absolute_path, to)
31+
end
2632
if not success then
2733
return notify.warn(err_fmt(node.absolute_path, to, err))
2834
end
2935
notify.info(node.absolute_path .. "" .. to)
36+
if use_async then
37+
async.schedule()
38+
end
3039
utils.rename_loaded_buffers(node.absolute_path, to)
3140
events._dispatch_node_renamed(node.absolute_path, to)
3241
end
3342

3443
function M.fn(default_modifier)
3544
default_modifier = default_modifier or ":t"
3645

37-
return function(node, modifier)
46+
return function(node, modifier, cb)
3847
if type(node) ~= "table" then
3948
node = lib.get_node_at_cursor()
4049
end
@@ -76,17 +85,31 @@ function M.fn(default_modifier)
7685
if not new_file_path then
7786
return
7887
end
79-
80-
M.rename(node, prepend .. new_file_path .. append)
81-
if M.enable_reload then
82-
require("nvim-tree.actions.reloaders.reloaders").reload_explorer()
88+
if M.enable_async then
89+
async.exec(M.rename, node, prepend .. new_file_path .. append, true, function(err)
90+
if err then
91+
notify.error(err)
92+
end
93+
if M.enable_reload then
94+
require("nvim-tree.actions.reloaders.reloaders").reload_explorer()
95+
end
96+
if cb then
97+
cb(err)
98+
end
99+
end)
100+
else
101+
M.rename(node, prepend .. new_file_path .. append, false)
102+
if M.enable_reload then
103+
require("nvim-tree.actions.reloaders.reloaders").reload_explorer()
104+
end
83105
end
84106
end)
85107
end
86108
end
87109

88110
function M.setup(opts)
89111
M.enable_reload = not opts.filesystem_watchers.enable
112+
M.enable_async = opts.experimental.async.rename_file
90113
end
91114

92115
return M

lua/nvim-tree/api.lua

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ local Api = {
66
fs = { copy = {} },
77
git = {},
88
live_filter = {},
9+
async = {},
910
}
1011

1112
local function inject_node(f)
@@ -113,6 +114,7 @@ Api.node.navigate.diagnostics.prev = inject_node(require("nvim-tree.actions.move
113114
Api.git.reload = require("nvim-tree.actions.reloaders.reloaders").reload_git
114115

115116
Api.events.subscribe = require("nvim-tree.events").subscribe
117+
Api.events.subscribe_async = require("nvim-tree.events").subscribe_async
116118
Api.events.Event = require("nvim-tree.events").Event
117119

118120
Api.live_filter.start = require("nvim-tree.live-filter").start_filtering
@@ -127,4 +129,12 @@ Api.marks.navigate.next = require("nvim-tree.marks.navigation").next
127129
Api.marks.navigate.prev = require("nvim-tree.marks.navigation").prev
128130
Api.marks.navigate.select = require("nvim-tree.marks.navigation").select
129131

132+
Api.async.call = function(...)
133+
if not require("nvim-tree.async").in_async() then
134+
error "The async call is not triggered in an async context of nvim-tree, please use `api.in_async` to detect beforehand"
135+
end
136+
require("nvim-tree.async").call(...)
137+
end
138+
Api.async.in_async = require("nvim-tree.async").in_async
139+
130140
return Api

0 commit comments

Comments
 (0)