-
Notifications
You must be signed in to change notification settings - Fork 138
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
perf(windows): use a single powershell process for trash operations (#…
…271) * perf(trash_windows): use a single powershell instance for operations * refactor(trash_windows): encapsulate powershell connection logic * refactor(windows_trash): better name for functions * fix(windows_trash): set connection error on initializatino if needed * refactor(windows_trash): simplify initialization code * refactor: extract some powershell logic into separate file --------- Co-authored-by: Steven Arcangeli <[email protected]>
- Loading branch information
Showing
3 changed files
with
197 additions
and
81 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
114 changes: 114 additions & 0 deletions
114
lua/oil/adapters/trash/windows/powershell-connection.lua
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
---@class (exact) oil.PowershellCommand | ||
---@field cmd string | ||
---@field cb fun(err?: string, output?: string) | ||
---@field running? boolean | ||
|
||
---@class oil.PowershellConnection | ||
---@field private jid integer | ||
---@field private execution_error? string | ||
---@field private commands oil.PowershellCommand[] | ||
---@field private stdout string[] | ||
---@field private is_reading_data boolean | ||
local PowershellConnection = {} | ||
|
||
---@param init_command? string | ||
---@return oil.PowershellConnection | ||
function PowershellConnection.new(init_command) | ||
local self = setmetatable({ | ||
commands = {}, | ||
stdout = {}, | ||
is_reading_data = false, | ||
}, { __index = PowershellConnection }) | ||
|
||
self:_init(init_command) | ||
|
||
return self | ||
end | ||
|
||
---@param init_command? string | ||
function PowershellConnection:_init(init_command) | ||
local jid = vim.fn.jobstart({ | ||
"powershell", | ||
"-NoProfile", | ||
"-NoLogo", | ||
"-ExecutionPolicy", | ||
"Bypass", | ||
"-NoExit", | ||
"-Command", | ||
"-", | ||
}, { | ||
---@param data string[] | ||
on_stdout = function(_, data) | ||
for _, fragment in ipairs(data) do | ||
if fragment:find("===DONE%((%a+)%)===") then | ||
self.is_reading_data = false | ||
local output = table.concat(self.stdout, "") | ||
local cb = self.commands[1].cb | ||
table.remove(self.commands, 1) | ||
local success = fragment:match("===DONE%((%a+)%)===") | ||
if success == "True" then | ||
cb(nil, output) | ||
elseif success == "False" then | ||
cb(success .. ": " .. output, output) | ||
end | ||
self.stdout = {} | ||
self:_consume() | ||
elseif self.is_reading_data then | ||
table.insert(self.stdout, fragment) | ||
end | ||
end | ||
end, | ||
}) | ||
|
||
if jid == 0 then | ||
self:_set_error("passed invalid arguments to 'powershell'") | ||
elseif jid == -1 then | ||
self:_set_error("'powershell' is not executable") | ||
else | ||
self.jid = jid | ||
end | ||
|
||
if init_command then | ||
table.insert(self.commands, { cmd = init_command, cb = function() end }) | ||
self:_consume() | ||
end | ||
end | ||
|
||
---@param command string | ||
---@param cb fun(err?: string, output?: string[]) | ||
function PowershellConnection:run(command, cb) | ||
if self.execution_error then | ||
cb(self.execution_error) | ||
else | ||
table.insert(self.commands, { cmd = command, cb = cb }) | ||
self:_consume() | ||
end | ||
end | ||
|
||
function PowershellConnection:_consume() | ||
if not vim.tbl_isempty(self.commands) then | ||
local cmd = self.commands[1] | ||
if not cmd.running then | ||
cmd.running = true | ||
self.is_reading_data = true | ||
-- $? contains the execution status of the last command. | ||
-- see https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_automatic_variables?view=powershell-7.4#section-1 | ||
vim.api.nvim_chan_send(self.jid, cmd.cmd .. '\nWrite-Host "===DONE($?)==="\n') | ||
end | ||
end | ||
end | ||
|
||
---@param err string | ||
function PowershellConnection:_set_error(err) | ||
if self.execution_error then | ||
return | ||
end | ||
self.execution_error = err | ||
local commands = self.commands | ||
self.commands = {} | ||
for _, cmd in ipairs(commands) do | ||
cmd.cb(err) | ||
end | ||
end | ||
|
||
return PowershellConnection |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
-- A wrapper around trash operations using windows powershell | ||
local Powershell = require("oil.adapters.trash.windows.powershell-connection") | ||
|
||
---@class oil.WindowsRawEntry | ||
---@field IsFolder boolean | ||
---@field DeletionDate integer | ||
---@field Name string | ||
---@field Path string | ||
---@field OriginalPath string | ||
|
||
local M = {} | ||
|
||
-- The first line configures Windows Powershell to use UTF-8 for input and output | ||
-- 0xa is the constant for Recycle Bin. See https://learn.microsoft.com/en-us/windows/win32/api/shldisp/ne-shldisp-shellspecialfolderconstants | ||
local list_entries_init = [[ | ||
$OutputEncoding = [Console]::InputEncoding = [Console]::OutputEncoding = New-Object System.Text.UTF8Encoding | ||
$shell = New-Object -ComObject 'Shell.Application' | ||
$folder = $shell.NameSpace(0xa) | ||
]] | ||
|
||
local list_entries_cmd = [[ | ||
$data = @(foreach ($i in $folder.items()) | ||
{ | ||
@{ | ||
IsFolder=$i.IsFolder; | ||
DeletionDate=([DateTimeOffset]$i.extendedproperty('datedeleted')).ToUnixTimeSeconds(); | ||
Name=$i.Name; | ||
Path=$i.Path; | ||
OriginalPath=-join($i.ExtendedProperty('DeletedFrom'), "\", $i.Name) | ||
} | ||
}) | ||
ConvertTo-Json $data -Compress | ||
]] | ||
|
||
---@type nil|oil.PowershellConnection | ||
local list_entries_powershell | ||
|
||
---@param cb fun(err?: string, raw_entries: oil.WindowsRawEntry[]?) | ||
M.list_raw_entries = function(cb) | ||
if not list_entries_powershell then | ||
list_entries_powershell = Powershell.new(list_entries_init) | ||
end | ||
list_entries_powershell:run(list_entries_cmd, function(err, string) | ||
if err then | ||
cb(err) | ||
return | ||
end | ||
|
||
local ok, value = pcall(vim.json.decode, string) | ||
if not ok then | ||
cb(value) | ||
return | ||
end | ||
cb(nil, value) | ||
end) | ||
end | ||
|
||
-- 0 is the constant for Windows Desktop. See https://learn.microsoft.com/en-us/windows/win32/api/shldisp/ne-shldisp-shellspecialfolderconstants | ||
local delete_init = [[ | ||
$shell = New-Object -ComObject 'Shell.Application' | ||
$folder = $shell.NameSpace(0) | ||
]] | ||
local delete_cmd = [[ | ||
$path = Get-Item '%s' | ||
$folder.ParseName($path.FullName).InvokeVerb('delete') | ||
]] | ||
|
||
---@type nil|oil.PowershellConnection | ||
local delete_to_trash_powershell | ||
|
||
---@param path string | ||
---@param cb fun(err?: string) | ||
M.delete_to_trash = function(path, cb) | ||
if not delete_to_trash_powershell then | ||
delete_to_trash_powershell = Powershell.new(delete_init) | ||
end | ||
delete_to_trash_powershell:run((delete_cmd):format(path:gsub("'", "''")), cb) | ||
end | ||
|
||
return M |