From 08d89689ec5e2d4bdbef561e8640d026b2c82333 Mon Sep 17 00:00:00 2001 From: Ya Qia Date: Sat, 7 Feb 2026 01:11:46 +0800 Subject: [PATCH 1/9] fix(RemoteTreeBrowser): add `bash -c` prefix --- lua/async-remote-write/browse.lua | 21 +++++++++++++-------- lua/async-remote-write/tree_browser.lua | 4 +++- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/lua/async-remote-write/browse.lua b/lua/async-remote-write/browse.lua index fd63319..76441ec 100644 --- a/lua/async-remote-write/browse.lua +++ b/lua/async-remote-write/browse.lua @@ -353,11 +353,12 @@ function M.warm_single_directory(dir_url, job, callback) end -- Use same command as level-based browser + -- Wrap in bash -c to ensure POSIX-compatible syntax works on all systems (e.g., fish shell) local bash_cmd = [[ - cd %s 2>/dev/null && \ + bash -c 'cd %s 2>/dev/null && \ find . -maxdepth 1 -not -name "." | while read f; do \ if [ -d "$f" ]; then echo "d $f"; else echo "f $f"; fi \ - done | sort + done | sort' ]] local cmd = { "ssh", host, string.format(bash_cmd, vim.fn.shellescape(path)) } @@ -557,13 +558,14 @@ function M.browse_remote_directory(url, reset_selections) end -- Use a bash script that's compatible with most systems + -- Wrap in bash -c to ensure POSIX-compatible syntax works on all systems (e.g., fish shell) local bash_cmd = [[ - cd %s && \ + bash -c 'cd %s && \ find . -maxdepth 1 | sort | while read f; do if [ "$f" != "." ]; then if [ -d "$f" ]; then echo "d ${f#./}"; else echo "f ${f#./}"; fi fi - done + done' ]] local cmd = { "ssh", host, string.format(bash_cmd, vim.fn.shellescape(path)) } @@ -1340,11 +1342,12 @@ function M.browse_remote_level_based(url, reset_selections) end -- Use maxdepth 1 to get ONLY immediate children + -- Wrap in bash -c to ensure POSIX-compatible syntax works on all systems (e.g., fish shell) local bash_cmd = [[ - cd %s 2>/dev/null && \ + bash -c 'cd %s 2>/dev/null && \ find . -maxdepth 1 -not -name "." | while read f; do \ if [ -d "$f" ]; then echo "d $f"; else echo "f $f"; fi \ - done | sort + done | sort' ]] local cmd = { "ssh", host, string.format(bash_cmd, vim.fn.shellescape(path)) } @@ -1576,8 +1579,9 @@ function M.load_directory_for_tree(url, depth, callback) end -- Build the SSH command + -- Wrap in bash -c to ensure POSIX-compatible syntax works on all systems (e.g., fish shell) local ssh_cmd = string.format( - 'cd %s && find . -maxdepth 1 | sort | while read f; do if [ "$f" != "." ]; then if [ -d "$f" ]; then echo "d ${f#./}"; else echo "f ${f#./}"; fi; fi; done', + [[bash -c 'cd %s && find . -maxdepth 1 | sort | while read f; do if [ "$f" != "." ]; then if [ -d "$f" ]; then echo "d ${f#./}"; else echo "f ${f#./}"; fi; fi; done']], vim.fn.shellescape(path) ) @@ -4001,8 +4005,9 @@ function M.load_directory_v2(url, callback) path = path .. "/" end + -- Wrap in bash -c to ensure POSIX-compatible syntax works on all systems (e.g., fish shell) local ssh_cmd = string.format( - 'cd %s && find . -maxdepth 1 | sort | while read f; do if [ "$f" != "." ]; then if [ -d "$f" ]; then echo "d ${f#./}"; else echo "f ${f#./}"; fi; fi; done', + [[bash -c 'cd %s && find . -maxdepth 1 | sort | while read f; do if [ "$f" != "." ]; then if [ -d "$f" ]; then echo "d ${f#./}"; else echo "f ${f#./}"; fi; fi; done']], vim.fn.shellescape(path) ) diff --git a/lua/async-remote-write/tree_browser.lua b/lua/async-remote-write/tree_browser.lua index a794e3f..57e180c 100644 --- a/lua/async-remote-write/tree_browser.lua +++ b/lua/async-remote-write/tree_browser.lua @@ -406,8 +406,10 @@ local function load_directory(url, callback) path = path .. "/" end + -- Use bash -c to ensure POSIX-compatible shell syntax works on all systems + -- (e.g., fish shell doesn't support ${var#pattern} syntax) local ssh_cmd = string.format( - 'cd %s && find . -maxdepth 1 | sort | while read f; do if [ "$f" != "." ]; then if [ -d "$f" ]; then echo "d ${f#./}"; else echo "f ${f#./}"; fi; fi; done', + [[bash -c 'cd %s && find . -maxdepth 1 | sort | while read f; do if [ "$f" != "." ]; then if [ -d "$f" ]; then echo "d ${f#./}"; else echo "f ${f#./}"; fi; fi; done']], vim.fn.shellescape(path) ) From 5b1a4d11c7032e973201431ac5295126bc2b366e Mon Sep 17 00:00:00 2001 From: Ya Qia Date: Sat, 7 Feb 2026 01:31:52 +0800 Subject: [PATCH 2/9] fix(RemoteTreeBrowser): single quoted path could be parsed now --- lua/async-remote-write/browse.lua | 36 ++++++++++--------------- lua/async-remote-write/tree_browser.lua | 3 ++- 2 files changed, 16 insertions(+), 23 deletions(-) diff --git a/lua/async-remote-write/browse.lua b/lua/async-remote-write/browse.lua index 76441ec..f988786 100644 --- a/lua/async-remote-write/browse.lua +++ b/lua/async-remote-write/browse.lua @@ -354,12 +354,9 @@ function M.warm_single_directory(dir_url, job, callback) -- Use same command as level-based browser -- Wrap in bash -c to ensure POSIX-compatible syntax works on all systems (e.g., fish shell) - local bash_cmd = [[ - bash -c 'cd %s 2>/dev/null && \ - find . -maxdepth 1 -not -name "." | while read f; do \ - if [ -d "$f" ]; then echo "d $f"; else echo "f $f"; fi \ - done | sort' - ]] + -- Pass path as an argument to avoid quoting issues with special characters + local bash_cmd = + [[bash -c 'cd "$1" 2>/dev/null && find . -maxdepth 1 -not -name "." | while read f; do if [ -d "$f" ]; then echo "d $f"; else echo "f $f"; fi; done | sort' _ %s]] local cmd = { "ssh", host, string.format(bash_cmd, vim.fn.shellescape(path)) } local output = {} @@ -559,14 +556,9 @@ function M.browse_remote_directory(url, reset_selections) -- Use a bash script that's compatible with most systems -- Wrap in bash -c to ensure POSIX-compatible syntax works on all systems (e.g., fish shell) - local bash_cmd = [[ - bash -c 'cd %s && \ - find . -maxdepth 1 | sort | while read f; do - if [ "$f" != "." ]; then - if [ -d "$f" ]; then echo "d ${f#./}"; else echo "f ${f#./}"; fi - fi - done' - ]] + -- Pass path as an argument to avoid quoting issues with special characters + local bash_cmd = + [[bash -c 'cd "$1" && find . -maxdepth 1 | sort | while read f; do if [ "$f" != "." ]; then if [ -d "$f" ]; then echo "d ${f#./}"; else echo "f ${f#./}"; fi; fi; done' _ %s]] local cmd = { "ssh", host, string.format(bash_cmd, vim.fn.shellescape(path)) } @@ -1343,12 +1335,10 @@ function M.browse_remote_level_based(url, reset_selections) -- Use maxdepth 1 to get ONLY immediate children -- Wrap in bash -c to ensure POSIX-compatible syntax works on all systems (e.g., fish shell) - local bash_cmd = [[ - bash -c 'cd %s 2>/dev/null && \ - find . -maxdepth 1 -not -name "." | while read f; do \ - if [ -d "$f" ]; then echo "d $f"; else echo "f $f"; fi \ - done | sort' - ]] + -- Wrap in bash -c to ensure POSIX-compatible syntax works on all systems (e.g., fish shell) + -- Pass path as an argument to avoid quoting issues with special characters + local bash_cmd = + [[bash -c 'cd "$1" 2>/dev/null && find . -maxdepth 1 -not -name "." | while read f; do if [ -d "$f" ]; then echo "d $f"; else echo "f $f"; fi; done | sort' _ %s]] local cmd = { "ssh", host, string.format(bash_cmd, vim.fn.shellescape(path)) } local output = {} @@ -1580,8 +1570,9 @@ function M.load_directory_for_tree(url, depth, callback) -- Build the SSH command -- Wrap in bash -c to ensure POSIX-compatible syntax works on all systems (e.g., fish shell) + -- Pass path as an argument to avoid quoting issues with special characters local ssh_cmd = string.format( - [[bash -c 'cd %s && find . -maxdepth 1 | sort | while read f; do if [ "$f" != "." ]; then if [ -d "$f" ]; then echo "d ${f#./}"; else echo "f ${f#./}"; fi; fi; done']], + [[bash -c 'cd "$1" && find . -maxdepth 1 | sort | while read f; do if [ "$f" != "." ]; then if [ -d "$f" ]; then echo "d ${f#./}"; else echo "f ${f#./}"; fi; fi; done' _ %s]], vim.fn.shellescape(path) ) @@ -4006,8 +3997,9 @@ function M.load_directory_v2(url, callback) end -- Wrap in bash -c to ensure POSIX-compatible syntax works on all systems (e.g., fish shell) + -- Pass path as an argument to avoid quoting issues with special characters local ssh_cmd = string.format( - [[bash -c 'cd %s && find . -maxdepth 1 | sort | while read f; do if [ "$f" != "." ]; then if [ -d "$f" ]; then echo "d ${f#./}"; else echo "f ${f#./}"; fi; fi; done']], + [[bash -c 'cd "$1" && find . -maxdepth 1 | sort | while read f; do if [ "$f" != "." ]; then if [ -d "$f" ]; then echo "d ${f#./}"; else echo "f ${f#./}"; fi; fi; done' _ %s]], vim.fn.shellescape(path) ) diff --git a/lua/async-remote-write/tree_browser.lua b/lua/async-remote-write/tree_browser.lua index 57e180c..a53d4fe 100644 --- a/lua/async-remote-write/tree_browser.lua +++ b/lua/async-remote-write/tree_browser.lua @@ -408,8 +408,9 @@ local function load_directory(url, callback) -- Use bash -c to ensure POSIX-compatible shell syntax works on all systems -- (e.g., fish shell doesn't support ${var#pattern} syntax) + -- Pass path as an argument to avoid quoting issues with special characters local ssh_cmd = string.format( - [[bash -c 'cd %s && find . -maxdepth 1 | sort | while read f; do if [ "$f" != "." ]; then if [ -d "$f" ]; then echo "d ${f#./}"; else echo "f ${f#./}"; fi; fi; done']], + [[bash -c 'cd "$1" && find . -maxdepth 1 | sort | while read f; do if [ "$f" != "." ]; then if [ -d "$f" ]; then echo "d ${f#./}"; else echo "f ${f#./}"; fi; fi; done' _ %s]], vim.fn.shellescape(path) ) From ead629589cfc32974f95de7b6d046d7a927c14cf Mon Sep 17 00:00:00 2001 From: Ya Qia Date: Sat, 7 Feb 2026 01:47:32 +0800 Subject: [PATCH 3/9] fix(RemoteTreeBrowser): use sh instead of bash 1. replace bash -c with sh -c, it should work natively. 2. give shell script strings a better format. --- lua/async-remote-write/browse.lua | 98 +++++++++++++++++-------- lua/async-remote-write/tree_browser.lua | 20 +++-- 2 files changed, 83 insertions(+), 35 deletions(-) diff --git a/lua/async-remote-write/browse.lua b/lua/async-remote-write/browse.lua index f988786..863b6a3 100644 --- a/lua/async-remote-write/browse.lua +++ b/lua/async-remote-write/browse.lua @@ -353,12 +353,20 @@ function M.warm_single_directory(dir_url, job, callback) end -- Use same command as level-based browser - -- Wrap in bash -c to ensure POSIX-compatible syntax works on all systems (e.g., fish shell) - -- Pass path as an argument to avoid quoting issues with special characters - local bash_cmd = - [[bash -c 'cd "$1" 2>/dev/null && find . -maxdepth 1 -not -name "." | while read f; do if [ -d "$f" ]; then echo "d $f"; else echo "f $f"; fi; done | sort' _ %s]] + -- Use sh -c to ensure POSIX-compatible syntax works on all systems (e.g., fish shell) + -- Pass path as argument ($1) to avoid quoting issues with special characters + local sh_script = [[ +cd "$1" 2>/dev/null && find . -maxdepth 1 -not -name "." | while read f; do + if [ -d "$f" ]; then + echo "d $f" + else + echo "f $f" + fi +done | sort +]] + local sh_cmd = string.format("sh -c %s _ %s", vim.fn.shellescape(sh_script), vim.fn.shellescape(path)) - local cmd = { "ssh", host, string.format(bash_cmd, vim.fn.shellescape(path)) } + local cmd = { "ssh", host, sh_cmd } local output = {} local job_id = vim.fn.jobstart(cmd, { @@ -554,13 +562,22 @@ function M.browse_remote_directory(url, reset_selections) return end - -- Use a bash script that's compatible with most systems - -- Wrap in bash -c to ensure POSIX-compatible syntax works on all systems (e.g., fish shell) - -- Pass path as an argument to avoid quoting issues with special characters - local bash_cmd = - [[bash -c 'cd "$1" && find . -maxdepth 1 | sort | while read f; do if [ "$f" != "." ]; then if [ -d "$f" ]; then echo "d ${f#./}"; else echo "f ${f#./}"; fi; fi; done' _ %s]] + -- Use sh -c to ensure POSIX-compatible syntax works on all systems (e.g., fish shell) + -- Pass path as argument ($1) to avoid quoting issues with special characters + local sh_script = [[ +cd "$1" && find . -maxdepth 1 | sort | while read f; do + if [ "$f" != "." ]; then + if [ -d "$f" ]; then + echo "d ${f#./}" + else + echo "f ${f#./}" + fi + fi +done +]] + local sh_cmd = string.format("sh -c %s _ %s", vim.fn.shellescape(sh_script), vim.fn.shellescape(path)) - local cmd = { "ssh", host, string.format(bash_cmd, vim.fn.shellescape(path)) } + local cmd = { "ssh", host, sh_cmd } -- Create job to execute command local output = {} @@ -1334,13 +1351,20 @@ function M.browse_remote_level_based(url, reset_selections) end -- Use maxdepth 1 to get ONLY immediate children - -- Wrap in bash -c to ensure POSIX-compatible syntax works on all systems (e.g., fish shell) - -- Wrap in bash -c to ensure POSIX-compatible syntax works on all systems (e.g., fish shell) - -- Pass path as an argument to avoid quoting issues with special characters - local bash_cmd = - [[bash -c 'cd "$1" 2>/dev/null && find . -maxdepth 1 -not -name "." | while read f; do if [ -d "$f" ]; then echo "d $f"; else echo "f $f"; fi; done | sort' _ %s]] + -- Use sh -c to ensure POSIX-compatible syntax works on all systems (e.g., fish shell) + -- Pass path as argument ($1) to avoid quoting issues with special characters + local sh_script = [[ +cd "$1" 2>/dev/null && find . -maxdepth 1 -not -name "." | while read f; do + if [ -d "$f" ]; then + echo "d $f" + else + echo "f $f" + fi +done | sort +]] + local sh_cmd = string.format("sh -c %s _ %s", vim.fn.shellescape(sh_script), vim.fn.shellescape(path)) - local cmd = { "ssh", host, string.format(bash_cmd, vim.fn.shellescape(path)) } + local cmd = { "ssh", host, sh_cmd } local output = {} local stderr_output = {} @@ -1569,12 +1593,20 @@ function M.load_directory_for_tree(url, depth, callback) end -- Build the SSH command - -- Wrap in bash -c to ensure POSIX-compatible syntax works on all systems (e.g., fish shell) - -- Pass path as an argument to avoid quoting issues with special characters - local ssh_cmd = string.format( - [[bash -c 'cd "$1" && find . -maxdepth 1 | sort | while read f; do if [ "$f" != "." ]; then if [ -d "$f" ]; then echo "d ${f#./}"; else echo "f ${f#./}"; fi; fi; done' _ %s]], - vim.fn.shellescape(path) - ) + -- Use sh -c to ensure POSIX-compatible syntax works on all systems (e.g., fish shell) + -- Pass path as argument ($1) to avoid quoting issues with special characters + local sh_script = [[ +cd "$1" && find . -maxdepth 1 | sort | while read f; do + if [ "$f" != "." ]; then + if [ -d "$f" ]; then + echo "d ${f#./}" + else + echo "f ${f#./}" + fi + fi +done +]] + local ssh_cmd = string.format("sh -c %s _ %s", vim.fn.shellescape(sh_script), vim.fn.shellescape(path)) local output = {} local stderr_output = {} @@ -3996,12 +4028,20 @@ function M.load_directory_v2(url, callback) path = path .. "/" end - -- Wrap in bash -c to ensure POSIX-compatible syntax works on all systems (e.g., fish shell) - -- Pass path as an argument to avoid quoting issues with special characters - local ssh_cmd = string.format( - [[bash -c 'cd "$1" && find . -maxdepth 1 | sort | while read f; do if [ "$f" != "." ]; then if [ -d "$f" ]; then echo "d ${f#./}"; else echo "f ${f#./}"; fi; fi; done' _ %s]], - vim.fn.shellescape(path) - ) + -- Use sh -c to ensure POSIX-compatible syntax works on all systems (e.g., fish shell) + -- Pass path as argument ($1) to avoid quoting issues with special characters + local sh_script = [[ +cd "$1" && find . -maxdepth 1 | sort | while read f; do + if [ "$f" != "." ]; then + if [ -d "$f" ]; then + echo "d ${f#./}" + else + echo "f ${f#./}" + fi + fi +done +]] + local ssh_cmd = string.format("sh -c %s _ %s", vim.fn.shellescape(sh_script), vim.fn.shellescape(path)) local output = {} local stderr_output = {} diff --git a/lua/async-remote-write/tree_browser.lua b/lua/async-remote-write/tree_browser.lua index a53d4fe..8c36d99 100644 --- a/lua/async-remote-write/tree_browser.lua +++ b/lua/async-remote-write/tree_browser.lua @@ -406,13 +406,21 @@ local function load_directory(url, callback) path = path .. "/" end - -- Use bash -c to ensure POSIX-compatible shell syntax works on all systems + -- Use sh -c to ensure POSIX-compatible shell syntax works on all systems -- (e.g., fish shell doesn't support ${var#pattern} syntax) - -- Pass path as an argument to avoid quoting issues with special characters - local ssh_cmd = string.format( - [[bash -c 'cd "$1" && find . -maxdepth 1 | sort | while read f; do if [ "$f" != "." ]; then if [ -d "$f" ]; then echo "d ${f#./}"; else echo "f ${f#./}"; fi; fi; done' _ %s]], - vim.fn.shellescape(path) - ) + -- Pass path as argument ($1) to avoid quoting issues with special characters + local sh_script = [[ +cd "$1" && find . -maxdepth 1 | sort | while read f; do + if [ "$f" != "." ]; then + if [ -d "$f" ]; then + echo "d ${f#./}" + else + echo "f ${f#./}" + fi + fi +done +]] + local ssh_cmd = string.format("sh -c %s _ %s", vim.fn.shellescape(sh_script), vim.fn.shellescape(path)) local output = {} local stderr_output = {} From 87da4753be0ea2bf585903e6c3f46187593af022 Mon Sep 17 00:00:00 2001 From: Ya Qia Date: Sat, 7 Feb 2026 02:08:09 +0800 Subject: [PATCH 4/9] fix(RemoteTreeBrowser): use the same logic in test files --- tests/test_file_browser_debug.lua | 39 ++++++++++----- tests/test_file_browser_ssh.lua | 22 ++++++--- tests/test_ssh_command_escaping.lua | 71 ++++++++++++++-------------- tests/test_ssh_robust_connection.lua | 26 ++++++---- 4 files changed, 94 insertions(+), 64 deletions(-) diff --git a/tests/test_file_browser_debug.lua b/tests/test_file_browser_debug.lua index 4c51945..c2c2e8f 100644 --- a/tests/test_file_browser_debug.lua +++ b/tests/test_file_browser_debug.lua @@ -1,6 +1,25 @@ -- Debug test for file browser SSH issues local test = require("tests.init") +-- Helper function to build SSH command (mirrors the actual implementation) +local function build_sh_script() + return [[ +cd "$1" && find . -maxdepth 1 | sort | while read f; do + if [ "$f" != "." ]; then + if [ -d "$f" ]; then + echo "d ${f#./}" + else + echo "f ${f#./}" + fi + fi +done +]] +end + +local function build_sh_command(path) + return string.format("sh -c %s _ %s", vim.fn.shellescape(build_sh_script()), vim.fn.shellescape(path)) +end + test.describe("File Browser Debug Tests", function() test.it("should simulate the exact tree browser load_directory scenario", function() -- Mock the exact URL and parsing that happens in the tree browser @@ -19,13 +38,10 @@ test.describe("File Browser Debug Tests", function() path = path .. "/" end - -- Build the exact SSH command from tree_browser.lua - local ssh_cmd = string.format( - 'cd %s && find . -maxdepth 1 | sort | while read f; do if [ "$f" != "." ]; then if [ -d "$f" ]; then echo "d ${f#./}"; else echo "f ${f#./}"; fi; fi; done', - vim.fn.shellescape(path) - ) + -- Build the SSH command using the new sh -c format + local ssh_cmd = build_sh_command(path) - test.assert.contains(ssh_cmd, "cd", "SSH command should contain cd") + test.assert.contains(ssh_cmd, "sh -c", "SSH command should use sh -c") test.assert.contains(ssh_cmd, "/home/testuser/repos/tokio/", "SSH command should contain the path") test.assert.contains(ssh_cmd, "find . -maxdepth 1", "SSH command should contain find") @@ -136,8 +152,7 @@ test.describe("File Browser Debug Tests", function() local host = "testuser@localhost" local exit_code = 255 local stderr_output = { "Connection closed by ::1 port 22" } - local ssh_cmd = - 'cd \'/home/testuser/repos/tokio/\' && find . -maxdepth 1 | sort | while read f; do if [ "$f" != "." ]; then if [ -d "$f" ]; then echo "d ${f#./}"; else echo "f ${f#./}"; fi; fi; done' + local ssh_cmd = build_sh_command("/home/testuser/repos/tokio/") -- Build error message like tree_browser.lua does local error_msg = "Failed to list directory: " .. url .. " (exit code: " .. exit_code .. ")" @@ -154,20 +169,20 @@ test.describe("File Browser Debug Tests", function() test.it("should test the actual SSH command construction with IPv4", function() local host = "testuser@localhost" - local command = "cd '/home/testuser/repos/tokio/' && find . -maxdepth 1" + local command = build_sh_command("/home/testuser/repos/tokio/") -- Mock ssh_utils.build_ssh_cmd behavior - local function build_ssh_cmd(host, cmd) + local function build_ssh_cmd(h, cmd) local ssh_args = { "ssh" } -- Check if host contains localhost (even with user@) - local is_localhost = host:match("localhost") or host:match("127%.0%.0%.1") or host:match("::1") + local is_localhost = h:match("localhost") or h:match("127%.0%.0%.1") or h:match("::1") if is_localhost then table.insert(ssh_args, "-4") end - table.insert(ssh_args, host) + table.insert(ssh_args, h) table.insert(ssh_args, cmd) return ssh_args diff --git a/tests/test_file_browser_ssh.lua b/tests/test_file_browser_ssh.lua index 4668646..757b650 100644 --- a/tests/test_file_browser_ssh.lua +++ b/tests/test_file_browser_ssh.lua @@ -152,14 +152,22 @@ test.describe("File Browser SSH Commands", function() test.it("should construct directory listing command correctly", function() local path = "/home/user/test/" - local escaped_path = vim.fn.shellescape(path) - local ssh_cmd = string.format( - 'cd %s && find . -maxdepth 1 | sort | while read f; do if [ "$f" != "." ]; then if [ -d "$f" ]; then echo "d ${f#./}"; else echo "f ${f#./}"; fi; fi; done', - escaped_path - ) - - test.assert.contains(ssh_cmd, "cd", "Command should contain cd") + -- Build the SSH command using the new sh -c format + local sh_script = [[ +cd "$1" && find . -maxdepth 1 | sort | while read f; do + if [ "$f" != "." ]; then + if [ -d "$f" ]; then + echo "d ${f#./}" + else + echo "f ${f#./}" + fi + fi +done +]] + local ssh_cmd = string.format("sh -c %s _ %s", vim.fn.shellescape(sh_script), vim.fn.shellescape(path)) + + test.assert.contains(ssh_cmd, "sh -c", "Command should use sh -c") test.assert.contains(ssh_cmd, "find", "Command should contain find") test.assert.contains(ssh_cmd, "-maxdepth 1", "Command should contain maxdepth limit") test.assert.contains(ssh_cmd, "sort", "Command should contain sort") diff --git a/tests/test_ssh_command_escaping.lua b/tests/test_ssh_command_escaping.lua index 6853541..812a5c1 100644 --- a/tests/test_ssh_command_escaping.lua +++ b/tests/test_ssh_command_escaping.lua @@ -1,66 +1,56 @@ -- Test SSH command escaping functionality local test = require("tests.init") +-- Helper function to build SSH command (mirrors the actual implementation) +local function build_sh_command(path) + local sh_script = [[ +cd "$1" && find . -maxdepth 1 | sort | while read f; do + if [ "$f" != "." ]; then + if [ -d "$f" ]; then + echo "d ${f#./}" + else + echo "f ${f#./}" + fi + fi +done +]] + return string.format("sh -c %s _ %s", vim.fn.shellescape(sh_script), vim.fn.shellescape(path)) +end + test.describe("SSH Command Escaping", function() test.it("should properly escape paths with spaces", function() local path = "/home/user/My Documents/test dir/" - local escaped_path = vim.fn.shellescape(path, 1) - - -- Test that the escaped path contains proper quoting - test.assert.contains(escaped_path, "My Documents", "Escaped path should contain the directory name") - - -- Test SSH command construction - local ssh_cmd = string.format( - 'cd %s && find . -maxdepth 1 | sort | while read f; do if [ \\"\\$f\\" != \\".\\" ]; then if [ -d \\"\\$f\\" ]; then echo \\"d \\${f#./}\\"; else echo \\"f \\${f#./}\\"; fi; fi; done', - escaped_path - ) + local ssh_cmd = build_sh_command(path) -- Verify command structure - test.assert.contains(ssh_cmd, "cd", "SSH command should contain cd command") + test.assert.contains(ssh_cmd, "sh -c", "SSH command should use sh -c") test.assert.contains(ssh_cmd, "find", "SSH command should contain find command") - test.assert.contains(ssh_cmd, '\\"\\$f\\"', "SSH command should have properly escaped variables") + test.assert.contains(ssh_cmd, "My Documents", "SSH command should contain the path") end) test.it("should properly escape paths with quotes", function() local path = "/home/user/test's dir/" - local escaped_path = vim.fn.shellescape(path, 1) - - -- Test SSH command construction with quotes - local ssh_cmd = string.format( - 'cd %s && find . -maxdepth 1 | sort | while read f; do if [ \\"\\$f\\" != \\".\\" ]; then if [ -d \\"\\$f\\" ]; then echo \\"d \\${f#./}\\"; else echo \\"f \\${f#./}\\"; fi; fi; done', - escaped_path - ) + local ssh_cmd = build_sh_command(path) -- Verify command doesn't break with quotes - test.assert.contains(ssh_cmd, "cd", "SSH command should contain cd command") + test.assert.contains(ssh_cmd, "sh -c", "SSH command should use sh -c") test.assert.truthy(#ssh_cmd > 50, "SSH command should be properly constructed") + -- Path is passed as separate argument, so quotes don't break the script + test.assert.contains(ssh_cmd, "test", "SSH command should contain part of the path") end) test.it("should properly escape paths with special characters", function() local path = "/home/user/test (dir) & more/" - local escaped_path = vim.fn.shellescape(path, 1) - - -- Test SSH command construction with special characters - local ssh_cmd = string.format( - 'cd %s && find . -maxdepth 1 | sort | while read f; do if [ \\"\\$f\\" != \\".\\" ]; then if [ -d \\"\\$f\\" ]; then echo \\"d \\${f#./}\\"; else echo \\"f \\${f#./}\\"; fi; fi; done', - escaped_path - ) + local ssh_cmd = build_sh_command(path) -- Verify command structure is maintained - test.assert.contains(ssh_cmd, "cd", "SSH command should contain cd command") + test.assert.contains(ssh_cmd, "sh -c", "SSH command should use sh -c") test.assert.contains(ssh_cmd, "find", "SSH command should contain find command") - test.assert.contains(ssh_cmd, '\\"\\$f\\"', "SSH command should have properly escaped variables") end) test.it("should handle simple paths without breaking", function() local path = "/home/user/simple/" - local escaped_path = vim.fn.shellescape(path, 1) - - -- Test SSH command construction - local ssh_cmd = string.format( - 'cd %s && find . -maxdepth 1 | sort | while read f; do if [ \\"\\$f\\" != \\".\\" ]; then if [ -d \\"\\$f\\" ]; then echo \\"d \\${f#./}\\"; else echo \\"f \\${f#./}\\"; fi; fi; done', - escaped_path - ) + local ssh_cmd = build_sh_command(path) -- Verify command contains expected elements test.assert.contains(ssh_cmd, "/home/user/simple", "SSH command should contain the path") @@ -68,4 +58,13 @@ test.describe("SSH Command Escaping", function() test.assert.contains(ssh_cmd, "sort", "SSH command should contain sort") test.assert.contains(ssh_cmd, "while read f", "SSH command should contain while loop") end) + + test.it("should pass path as argument to avoid quoting issues", function() + local path = '/home/user/test\'s "quoted" dir/' + local ssh_cmd = build_sh_command(path) + + -- The key feature: path is passed as $1 argument, not embedded in script + test.assert.contains(ssh_cmd, "_ ", "SSH command should have placeholder for $0") + test.assert.contains(ssh_cmd, 'cd "$1"', "Script should reference $1 for path") + end) end) diff --git a/tests/test_ssh_robust_connection.lua b/tests/test_ssh_robust_connection.lua index 1845743..c8283aa 100644 --- a/tests/test_ssh_robust_connection.lua +++ b/tests/test_ssh_robust_connection.lua @@ -121,18 +121,26 @@ test.describe("SSH Robust Connection Options", function() local host = "ianhersom@raspi0" local path = "/home/ianhersom/repo/neovim/test/old/" - -- Build the SSH command that would be executed - local ssh_cmd = string.format( - 'cd %s && find . -maxdepth 1 | sort | while read f; do if [ "$f" != "." ]; then if [ -d "$f" ]; then echo "d ${f#./}"; else echo "f ${f#./}"; fi; fi; done', - vim.fn.shellescape(path) - ) - - test.assert.contains(ssh_cmd, "cd", "SSH command should contain cd") + -- Build the SSH command using the new sh -c format + local sh_script = [[ +cd "$1" && find . -maxdepth 1 | sort | while read f; do + if [ "$f" != "." ]; then + if [ -d "$f" ]; then + echo "d ${f#./}" + else + echo "f ${f#./}" + fi + fi +done +]] + local ssh_cmd = string.format("sh -c %s _ %s", vim.fn.shellescape(sh_script), vim.fn.shellescape(path)) + + test.assert.contains(ssh_cmd, "sh -c", "SSH command should use sh -c") test.assert.contains(ssh_cmd, "/home/ianhersom/repo/neovim/test/old/", "SSH command should contain the path") test.assert.contains(ssh_cmd, "find . -maxdepth 1", "SSH command should contain find") -- Mock robust SSH command construction - local function build_ssh_cmd(host, command) + local function build_ssh_cmd(h, command) local ssh_args = { "ssh" } table.insert(ssh_args, "-o") @@ -148,7 +156,7 @@ test.describe("SSH Robust Connection Options", function() table.insert(ssh_args, "-o") table.insert(ssh_args, "ControlPath=none") - table.insert(ssh_args, host) + table.insert(ssh_args, h) table.insert(ssh_args, command) return ssh_args From ae48e5cf2cf4d82ea2a05df5a36ede1075e11fb2 Mon Sep 17 00:00:00 2001 From: Ya Qia Date: Sat, 7 Feb 2026 02:12:05 +0800 Subject: [PATCH 5/9] Use 'IFS= read -r' to handle filenames with spaces/backslashes --- lua/async-remote-write/browse.lua | 10 +++++----- lua/async-remote-write/tree_browser.lua | 2 +- tests/test_file_browser_debug.lua | 2 +- tests/test_file_browser_ssh.lua | 4 ++-- tests/test_ssh_command_escaping.lua | 4 ++-- tests/test_ssh_robust_connection.lua | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lua/async-remote-write/browse.lua b/lua/async-remote-write/browse.lua index 863b6a3..b7e89b5 100644 --- a/lua/async-remote-write/browse.lua +++ b/lua/async-remote-write/browse.lua @@ -356,7 +356,7 @@ function M.warm_single_directory(dir_url, job, callback) -- Use sh -c to ensure POSIX-compatible syntax works on all systems (e.g., fish shell) -- Pass path as argument ($1) to avoid quoting issues with special characters local sh_script = [[ -cd "$1" 2>/dev/null && find . -maxdepth 1 -not -name "." | while read f; do +cd "$1" 2>/dev/null && find . -maxdepth 1 -not -name "." | while IFS= read -r f; do if [ -d "$f" ]; then echo "d $f" else @@ -565,7 +565,7 @@ function M.browse_remote_directory(url, reset_selections) -- Use sh -c to ensure POSIX-compatible syntax works on all systems (e.g., fish shell) -- Pass path as argument ($1) to avoid quoting issues with special characters local sh_script = [[ -cd "$1" && find . -maxdepth 1 | sort | while read f; do +cd "$1" && find . -maxdepth 1 | sort | while IFS= read -r f; do if [ "$f" != "." ]; then if [ -d "$f" ]; then echo "d ${f#./}" @@ -1354,7 +1354,7 @@ function M.browse_remote_level_based(url, reset_selections) -- Use sh -c to ensure POSIX-compatible syntax works on all systems (e.g., fish shell) -- Pass path as argument ($1) to avoid quoting issues with special characters local sh_script = [[ -cd "$1" 2>/dev/null && find . -maxdepth 1 -not -name "." | while read f; do +cd "$1" 2>/dev/null && find . -maxdepth 1 -not -name "." | while IFS= read -r f; do if [ -d "$f" ]; then echo "d $f" else @@ -1596,7 +1596,7 @@ function M.load_directory_for_tree(url, depth, callback) -- Use sh -c to ensure POSIX-compatible syntax works on all systems (e.g., fish shell) -- Pass path as argument ($1) to avoid quoting issues with special characters local sh_script = [[ -cd "$1" && find . -maxdepth 1 | sort | while read f; do +cd "$1" && find . -maxdepth 1 | sort | while IFS= read -r f; do if [ "$f" != "." ]; then if [ -d "$f" ]; then echo "d ${f#./}" @@ -4031,7 +4031,7 @@ function M.load_directory_v2(url, callback) -- Use sh -c to ensure POSIX-compatible syntax works on all systems (e.g., fish shell) -- Pass path as argument ($1) to avoid quoting issues with special characters local sh_script = [[ -cd "$1" && find . -maxdepth 1 | sort | while read f; do +cd "$1" && find . -maxdepth 1 | sort | while IFS= read -r f; do if [ "$f" != "." ]; then if [ -d "$f" ]; then echo "d ${f#./}" diff --git a/lua/async-remote-write/tree_browser.lua b/lua/async-remote-write/tree_browser.lua index 8c36d99..9bd3fd6 100644 --- a/lua/async-remote-write/tree_browser.lua +++ b/lua/async-remote-write/tree_browser.lua @@ -410,7 +410,7 @@ local function load_directory(url, callback) -- (e.g., fish shell doesn't support ${var#pattern} syntax) -- Pass path as argument ($1) to avoid quoting issues with special characters local sh_script = [[ -cd "$1" && find . -maxdepth 1 | sort | while read f; do +cd "$1" && find . -maxdepth 1 | sort | while IFS= read -r f; do if [ "$f" != "." ]; then if [ -d "$f" ]; then echo "d ${f#./}" diff --git a/tests/test_file_browser_debug.lua b/tests/test_file_browser_debug.lua index c2c2e8f..44fe2a6 100644 --- a/tests/test_file_browser_debug.lua +++ b/tests/test_file_browser_debug.lua @@ -4,7 +4,7 @@ local test = require("tests.init") -- Helper function to build SSH command (mirrors the actual implementation) local function build_sh_script() return [[ -cd "$1" && find . -maxdepth 1 | sort | while read f; do +cd "$1" && find . -maxdepth 1 | sort | while IFS= read -r f; do if [ "$f" != "." ]; then if [ -d "$f" ]; then echo "d ${f#./}" diff --git a/tests/test_file_browser_ssh.lua b/tests/test_file_browser_ssh.lua index 757b650..1551167 100644 --- a/tests/test_file_browser_ssh.lua +++ b/tests/test_file_browser_ssh.lua @@ -155,7 +155,7 @@ test.describe("File Browser SSH Commands", function() -- Build the SSH command using the new sh -c format local sh_script = [[ -cd "$1" && find . -maxdepth 1 | sort | while read f; do +cd "$1" && find . -maxdepth 1 | sort | while IFS= read -r f; do if [ "$f" != "." ]; then if [ -d "$f" ]; then echo "d ${f#./}" @@ -171,7 +171,7 @@ done test.assert.contains(ssh_cmd, "find", "Command should contain find") test.assert.contains(ssh_cmd, "-maxdepth 1", "Command should contain maxdepth limit") test.assert.contains(ssh_cmd, "sort", "Command should contain sort") - test.assert.contains(ssh_cmd, "while read", "Command should contain while loop") + test.assert.contains(ssh_cmd, "while IFS= read -r", "Command should contain while loop") test.assert.contains(ssh_cmd, 'echo "d', "Command should output directory marker") test.assert.contains(ssh_cmd, 'echo "f', "Command should output file marker") end) diff --git a/tests/test_ssh_command_escaping.lua b/tests/test_ssh_command_escaping.lua index 812a5c1..d490fa0 100644 --- a/tests/test_ssh_command_escaping.lua +++ b/tests/test_ssh_command_escaping.lua @@ -4,7 +4,7 @@ local test = require("tests.init") -- Helper function to build SSH command (mirrors the actual implementation) local function build_sh_command(path) local sh_script = [[ -cd "$1" && find . -maxdepth 1 | sort | while read f; do +cd "$1" && find . -maxdepth 1 | sort | while IFS= read -r f; do if [ "$f" != "." ]; then if [ -d "$f" ]; then echo "d ${f#./}" @@ -56,7 +56,7 @@ test.describe("SSH Command Escaping", function() test.assert.contains(ssh_cmd, "/home/user/simple", "SSH command should contain the path") test.assert.contains(ssh_cmd, "find . -maxdepth 1", "SSH command should contain find with maxdepth") test.assert.contains(ssh_cmd, "sort", "SSH command should contain sort") - test.assert.contains(ssh_cmd, "while read f", "SSH command should contain while loop") + test.assert.contains(ssh_cmd, "while IFS= read -r f", "SSH command should contain while loop") end) test.it("should pass path as argument to avoid quoting issues", function() diff --git a/tests/test_ssh_robust_connection.lua b/tests/test_ssh_robust_connection.lua index c8283aa..f1308cc 100644 --- a/tests/test_ssh_robust_connection.lua +++ b/tests/test_ssh_robust_connection.lua @@ -123,7 +123,7 @@ test.describe("SSH Robust Connection Options", function() -- Build the SSH command using the new sh -c format local sh_script = [[ -cd "$1" && find . -maxdepth 1 | sort | while read f; do +cd "$1" && find . -maxdepth 1 | sort | while IFS= read -r f; do if [ "$f" != "." ]; then if [ -d "$f" ]; then echo "d ${f#./}" From 35a6e44079b81d2d9d9dcd77906393d1122ca368 Mon Sep 17 00:00:00 2001 From: Ya Qia Date: Sat, 7 Feb 2026 02:41:39 +0800 Subject: [PATCH 6/9] Improve test assertion for quoted path escaping --- tests/test_ssh_command_escaping.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_ssh_command_escaping.lua b/tests/test_ssh_command_escaping.lua index d490fa0..a5c965b 100644 --- a/tests/test_ssh_command_escaping.lua +++ b/tests/test_ssh_command_escaping.lua @@ -32,11 +32,11 @@ test.describe("SSH Command Escaping", function() local path = "/home/user/test's dir/" local ssh_cmd = build_sh_command(path) - -- Verify command doesn't break with quotes + -- Verify command structure test.assert.contains(ssh_cmd, "sh -c", "SSH command should use sh -c") - test.assert.truthy(#ssh_cmd > 50, "SSH command should be properly constructed") - -- Path is passed as separate argument, so quotes don't break the script - test.assert.contains(ssh_cmd, "test", "SSH command should contain part of the path") + -- Verify the path is properly shell-escaped and passed as argument + local escaped_path = vim.fn.shellescape(path) + test.assert.contains(ssh_cmd, escaped_path, "SSH command should contain properly escaped path") end) test.it("should properly escape paths with special characters", function() From 0a933243dc428d7dbcd9ee1872f8f2fcce539361 Mon Sep 17 00:00:00 2001 From: Ya Qia Date: Sat, 7 Feb 2026 02:45:59 +0800 Subject: [PATCH 7/9] Refactor: extract directory listing command to ssh_utils --- lua/async-remote-write/browse.lua | 79 ++++--------------------- lua/async-remote-write/ssh_utils.lua | 37 ++++++++++++ lua/async-remote-write/tree_browser.lua | 17 +----- 3 files changed, 49 insertions(+), 84 deletions(-) diff --git a/lua/async-remote-write/browse.lua b/lua/async-remote-write/browse.lua index b7e89b5..c32a073 100644 --- a/lua/async-remote-write/browse.lua +++ b/lua/async-remote-write/browse.lua @@ -352,19 +352,8 @@ function M.warm_single_directory(dir_url, job, callback) path = path .. "/" end - -- Use same command as level-based browser - -- Use sh -c to ensure POSIX-compatible syntax works on all systems (e.g., fish shell) - -- Pass path as argument ($1) to avoid quoting issues with special characters - local sh_script = [[ -cd "$1" 2>/dev/null && find . -maxdepth 1 -not -name "." | while IFS= read -r f; do - if [ -d "$f" ]; then - echo "d $f" - else - echo "f $f" - fi -done | sort -]] - local sh_cmd = string.format("sh -c %s _ %s", vim.fn.shellescape(sh_script), vim.fn.shellescape(path)) + -- Use ssh_utils helper to build directory listing command + local sh_cmd = ssh_utils.build_list_dir_cmd(path, { sorted = false }) local cmd = { "ssh", host, sh_cmd } local output = {} @@ -562,20 +551,8 @@ function M.browse_remote_directory(url, reset_selections) return end - -- Use sh -c to ensure POSIX-compatible syntax works on all systems (e.g., fish shell) - -- Pass path as argument ($1) to avoid quoting issues with special characters - local sh_script = [[ -cd "$1" && find . -maxdepth 1 | sort | while IFS= read -r f; do - if [ "$f" != "." ]; then - if [ -d "$f" ]; then - echo "d ${f#./}" - else - echo "f ${f#./}" - fi - fi -done -]] - local sh_cmd = string.format("sh -c %s _ %s", vim.fn.shellescape(sh_script), vim.fn.shellescape(path)) + -- Use ssh_utils helper to build directory listing command + local sh_cmd = ssh_utils.build_list_dir_cmd(path) local cmd = { "ssh", host, sh_cmd } @@ -1350,19 +1327,8 @@ function M.browse_remote_level_based(url, reset_selections) return end - -- Use maxdepth 1 to get ONLY immediate children - -- Use sh -c to ensure POSIX-compatible syntax works on all systems (e.g., fish shell) - -- Pass path as argument ($1) to avoid quoting issues with special characters - local sh_script = [[ -cd "$1" 2>/dev/null && find . -maxdepth 1 -not -name "." | while IFS= read -r f; do - if [ -d "$f" ]; then - echo "d $f" - else - echo "f $f" - fi -done | sort -]] - local sh_cmd = string.format("sh -c %s _ %s", vim.fn.shellescape(sh_script), vim.fn.shellescape(path)) + -- Use ssh_utils helper to build directory listing command + local sh_cmd = ssh_utils.build_list_dir_cmd(path, { sorted = false }) local cmd = { "ssh", host, sh_cmd } local output = {} @@ -1592,21 +1558,8 @@ function M.load_directory_for_tree(url, depth, callback) path = path .. "/" end - -- Build the SSH command - -- Use sh -c to ensure POSIX-compatible syntax works on all systems (e.g., fish shell) - -- Pass path as argument ($1) to avoid quoting issues with special characters - local sh_script = [[ -cd "$1" && find . -maxdepth 1 | sort | while IFS= read -r f; do - if [ "$f" != "." ]; then - if [ -d "$f" ]; then - echo "d ${f#./}" - else - echo "f ${f#./}" - fi - fi -done -]] - local ssh_cmd = string.format("sh -c %s _ %s", vim.fn.shellescape(sh_script), vim.fn.shellescape(path)) + -- Use ssh_utils helper to build directory listing command + local ssh_cmd = ssh_utils.build_list_dir_cmd(path) local output = {} local stderr_output = {} @@ -4028,20 +3981,8 @@ function M.load_directory_v2(url, callback) path = path .. "/" end - -- Use sh -c to ensure POSIX-compatible syntax works on all systems (e.g., fish shell) - -- Pass path as argument ($1) to avoid quoting issues with special characters - local sh_script = [[ -cd "$1" && find . -maxdepth 1 | sort | while IFS= read -r f; do - if [ "$f" != "." ]; then - if [ -d "$f" ]; then - echo "d ${f#./}" - else - echo "f ${f#./}" - fi - fi -done -]] - local ssh_cmd = string.format("sh -c %s _ %s", vim.fn.shellescape(sh_script), vim.fn.shellescape(path)) + -- Use ssh_utils helper to build directory listing command + local ssh_cmd = ssh_utils.build_list_dir_cmd(path) local output = {} local stderr_output = {} diff --git a/lua/async-remote-write/ssh_utils.lua b/lua/async-remote-write/ssh_utils.lua index 202d666..00a9780 100644 --- a/lua/async-remote-write/ssh_utils.lua +++ b/lua/async-remote-write/ssh_utils.lua @@ -228,4 +228,41 @@ end -- Expose is_localhost for other modules that might need it M.is_localhost = is_localhost +-- Shell script for listing directory contents (sorted) +-- Uses sh -c to ensure POSIX shell compatibility (works with fish, zsh, etc.) +-- Uses IFS= read -r to handle filenames with spaces/backslashes +local LIST_DIR_SCRIPT = [[ +cd "$1" && find . -maxdepth 1 | sort | while IFS= read -r f; do + if [ "$f" != "." ]; then + if [ -d "$f" ]; then + echo "d ${f#./}" + else + echo "f ${f#./}" + fi + fi +done +]] + +-- Shell script for listing directory contents (unsorted, with error suppression) +local LIST_DIR_SCRIPT_UNSORTED = [[ +cd "$1" 2>/dev/null && find . -maxdepth 1 -not -name "." | while IFS= read -r f; do + if [ -d "$f" ]; then + echo "d ${f#./}" + else + echo "f ${f#./}" + fi +done +]] + +--- Build a shell command for listing directory contents via SSH +--- @param path string The remote directory path +--- @param opts? {sorted?: boolean} Options: sorted (default true) +--- @return string The shell command to execute +function M.build_list_dir_cmd(path, opts) + opts = opts or {} + local sorted = opts.sorted ~= false -- default to true + local script = sorted and LIST_DIR_SCRIPT or LIST_DIR_SCRIPT_UNSORTED + return string.format("sh -c %s _ %s", vim.fn.shellescape(script), vim.fn.shellescape(path)) +end + return M diff --git a/lua/async-remote-write/tree_browser.lua b/lua/async-remote-write/tree_browser.lua index 9bd3fd6..1f131af 100644 --- a/lua/async-remote-write/tree_browser.lua +++ b/lua/async-remote-write/tree_browser.lua @@ -406,21 +406,8 @@ local function load_directory(url, callback) path = path .. "/" end - -- Use sh -c to ensure POSIX-compatible shell syntax works on all systems - -- (e.g., fish shell doesn't support ${var#pattern} syntax) - -- Pass path as argument ($1) to avoid quoting issues with special characters - local sh_script = [[ -cd "$1" && find . -maxdepth 1 | sort | while IFS= read -r f; do - if [ "$f" != "." ]; then - if [ -d "$f" ]; then - echo "d ${f#./}" - else - echo "f ${f#./}" - fi - fi -done -]] - local ssh_cmd = string.format("sh -c %s _ %s", vim.fn.shellescape(sh_script), vim.fn.shellescape(path)) + -- Use ssh_utils helper to build directory listing command + local ssh_cmd = ssh_utils.build_list_dir_cmd(path) local output = {} local stderr_output = {} From 98814a7a26ab389c41265aa23bae451f55135f0d Mon Sep 17 00:00:00 2001 From: Ya Qia Date: Sat, 7 Feb 2026 02:51:32 +0800 Subject: [PATCH 8/9] Refactor: use ssh_utils.build_list_dir_cmd in tests --- tests/test_file_browser_debug.lua | 28 +++--------- tests/test_file_browser_ssh.lua | 66 ++-------------------------- tests/test_ssh_command_escaping.lua | 27 +++--------- tests/test_ssh_robust_connection.lua | 16 ++----- 4 files changed, 17 insertions(+), 120 deletions(-) diff --git a/tests/test_file_browser_debug.lua b/tests/test_file_browser_debug.lua index 44fe2a6..33e166a 100644 --- a/tests/test_file_browser_debug.lua +++ b/tests/test_file_browser_debug.lua @@ -1,24 +1,6 @@ -- Debug test for file browser SSH issues local test = require("tests.init") - --- Helper function to build SSH command (mirrors the actual implementation) -local function build_sh_script() - return [[ -cd "$1" && find . -maxdepth 1 | sort | while IFS= read -r f; do - if [ "$f" != "." ]; then - if [ -d "$f" ]; then - echo "d ${f#./}" - else - echo "f ${f#./}" - fi - fi -done -]] -end - -local function build_sh_command(path) - return string.format("sh -c %s _ %s", vim.fn.shellescape(build_sh_script()), vim.fn.shellescape(path)) -end +local ssh_utils = require("async-remote-write.ssh_utils") test.describe("File Browser Debug Tests", function() test.it("should simulate the exact tree browser load_directory scenario", function() @@ -38,8 +20,8 @@ test.describe("File Browser Debug Tests", function() path = path .. "/" end - -- Build the SSH command using the new sh -c format - local ssh_cmd = build_sh_command(path) + -- Build the SSH command using ssh_utils + local ssh_cmd = ssh_utils.build_list_dir_cmd(path) test.assert.contains(ssh_cmd, "sh -c", "SSH command should use sh -c") test.assert.contains(ssh_cmd, "/home/testuser/repos/tokio/", "SSH command should contain the path") @@ -152,7 +134,7 @@ test.describe("File Browser Debug Tests", function() local host = "testuser@localhost" local exit_code = 255 local stderr_output = { "Connection closed by ::1 port 22" } - local ssh_cmd = build_sh_command("/home/testuser/repos/tokio/") + local ssh_cmd = ssh_utils.build_list_dir_cmd("/home/testuser/repos/tokio/") -- Build error message like tree_browser.lua does local error_msg = "Failed to list directory: " .. url .. " (exit code: " .. exit_code .. ")" @@ -169,7 +151,7 @@ test.describe("File Browser Debug Tests", function() test.it("should test the actual SSH command construction with IPv4", function() local host = "testuser@localhost" - local command = build_sh_command("/home/testuser/repos/tokio/") + local command = ssh_utils.build_list_dir_cmd("/home/testuser/repos/tokio/") -- Mock ssh_utils.build_ssh_cmd behavior local function build_ssh_cmd(h, cmd) diff --git a/tests/test_file_browser_ssh.lua b/tests/test_file_browser_ssh.lua index 1551167..3c38cd2 100644 --- a/tests/test_file_browser_ssh.lua +++ b/tests/test_file_browser_ssh.lua @@ -1,55 +1,6 @@ -- Test file browser SSH functionality local test = require("tests.init") - --- Mock ssh_utils functions for testing -local ssh_utils = {} - -ssh_utils.is_localhost = function(host) - return host == "localhost" or host == "127.0.0.1" or host == "::1" -end - -ssh_utils.build_ssh_cmd = function(host, command) - local ssh_args = { "ssh" } - - -- Add IPv4 preference for localhost connections to avoid IPv6 issues - if ssh_utils.is_localhost(host) then - table.insert(ssh_args, "-4") - end - - table.insert(ssh_args, host) - table.insert(ssh_args, command) - - return ssh_args -end - -ssh_utils.build_scp_cmd = function(source, destination, options) - local scp_args = { "scp" } - - -- Add standard options - if options then - for _, opt in ipairs(options) do - table.insert(scp_args, opt) - end - end - - -- Extract host from source or destination to check for localhost - local host = nil - if source:match("^[^:]+:") then - host = source:match("^([^:]+):") - elseif destination:match("^[^:]+:") then - host = destination:match("^([^:]+):") - end - - -- Add IPv4 preference for localhost connections - if host and ssh_utils.is_localhost(host) then - table.insert(scp_args, "-4") - end - - table.insert(scp_args, source) - table.insert(scp_args, destination) - - return scp_args -end +local ssh_utils = require("async-remote-write.ssh_utils") test.describe("File Browser SSH Commands", function() test.it("should build SSH commands correctly for localhost", function() @@ -153,19 +104,8 @@ test.describe("File Browser SSH Commands", function() test.it("should construct directory listing command correctly", function() local path = "/home/user/test/" - -- Build the SSH command using the new sh -c format - local sh_script = [[ -cd "$1" && find . -maxdepth 1 | sort | while IFS= read -r f; do - if [ "$f" != "." ]; then - if [ -d "$f" ]; then - echo "d ${f#./}" - else - echo "f ${f#./}" - fi - fi -done -]] - local ssh_cmd = string.format("sh -c %s _ %s", vim.fn.shellescape(sh_script), vim.fn.shellescape(path)) + -- Build the SSH command using ssh_utils + local ssh_cmd = ssh_utils.build_list_dir_cmd(path) test.assert.contains(ssh_cmd, "sh -c", "Command should use sh -c") test.assert.contains(ssh_cmd, "find", "Command should contain find") diff --git a/tests/test_ssh_command_escaping.lua b/tests/test_ssh_command_escaping.lua index a5c965b..4f8d7a4 100644 --- a/tests/test_ssh_command_escaping.lua +++ b/tests/test_ssh_command_escaping.lua @@ -1,26 +1,11 @@ -- Test SSH command escaping functionality local test = require("tests.init") - --- Helper function to build SSH command (mirrors the actual implementation) -local function build_sh_command(path) - local sh_script = [[ -cd "$1" && find . -maxdepth 1 | sort | while IFS= read -r f; do - if [ "$f" != "." ]; then - if [ -d "$f" ]; then - echo "d ${f#./}" - else - echo "f ${f#./}" - fi - fi -done -]] - return string.format("sh -c %s _ %s", vim.fn.shellescape(sh_script), vim.fn.shellescape(path)) -end +local ssh_utils = require("async-remote-write.ssh_utils") test.describe("SSH Command Escaping", function() test.it("should properly escape paths with spaces", function() local path = "/home/user/My Documents/test dir/" - local ssh_cmd = build_sh_command(path) + local ssh_cmd = ssh_utils.build_list_dir_cmd(path) -- Verify command structure test.assert.contains(ssh_cmd, "sh -c", "SSH command should use sh -c") @@ -30,7 +15,7 @@ test.describe("SSH Command Escaping", function() test.it("should properly escape paths with quotes", function() local path = "/home/user/test's dir/" - local ssh_cmd = build_sh_command(path) + local ssh_cmd = ssh_utils.build_list_dir_cmd(path) -- Verify command structure test.assert.contains(ssh_cmd, "sh -c", "SSH command should use sh -c") @@ -41,7 +26,7 @@ test.describe("SSH Command Escaping", function() test.it("should properly escape paths with special characters", function() local path = "/home/user/test (dir) & more/" - local ssh_cmd = build_sh_command(path) + local ssh_cmd = ssh_utils.build_list_dir_cmd(path) -- Verify command structure is maintained test.assert.contains(ssh_cmd, "sh -c", "SSH command should use sh -c") @@ -50,7 +35,7 @@ test.describe("SSH Command Escaping", function() test.it("should handle simple paths without breaking", function() local path = "/home/user/simple/" - local ssh_cmd = build_sh_command(path) + local ssh_cmd = ssh_utils.build_list_dir_cmd(path) -- Verify command contains expected elements test.assert.contains(ssh_cmd, "/home/user/simple", "SSH command should contain the path") @@ -61,7 +46,7 @@ test.describe("SSH Command Escaping", function() test.it("should pass path as argument to avoid quoting issues", function() local path = '/home/user/test\'s "quoted" dir/' - local ssh_cmd = build_sh_command(path) + local ssh_cmd = ssh_utils.build_list_dir_cmd(path) -- The key feature: path is passed as $1 argument, not embedded in script test.assert.contains(ssh_cmd, "_ ", "SSH command should have placeholder for $0") diff --git a/tests/test_ssh_robust_connection.lua b/tests/test_ssh_robust_connection.lua index f1308cc..0e0ab6f 100644 --- a/tests/test_ssh_robust_connection.lua +++ b/tests/test_ssh_robust_connection.lua @@ -1,5 +1,6 @@ -- Test robust SSH connection options local test = require("tests.init") +local ssh_utils = require("async-remote-write.ssh_utils") test.describe("SSH Robust Connection Options", function() test.it("should build SSH commands with robust connection options", function() @@ -121,19 +122,8 @@ test.describe("SSH Robust Connection Options", function() local host = "ianhersom@raspi0" local path = "/home/ianhersom/repo/neovim/test/old/" - -- Build the SSH command using the new sh -c format - local sh_script = [[ -cd "$1" && find . -maxdepth 1 | sort | while IFS= read -r f; do - if [ "$f" != "." ]; then - if [ -d "$f" ]; then - echo "d ${f#./}" - else - echo "f ${f#./}" - fi - fi -done -]] - local ssh_cmd = string.format("sh -c %s _ %s", vim.fn.shellescape(sh_script), vim.fn.shellescape(path)) + -- Build the SSH command using ssh_utils + local ssh_cmd = ssh_utils.build_list_dir_cmd(path) test.assert.contains(ssh_cmd, "sh -c", "SSH command should use sh -c") test.assert.contains(ssh_cmd, "/home/ianhersom/repo/neovim/test/old/", "SSH command should contain the path") From e9d378445b205b014a9576ef472b186e51ebf2c5 Mon Sep 17 00:00:00 2001 From: Ya Qia Date: Sat, 7 Feb 2026 03:15:11 +0800 Subject: [PATCH 9/9] Improve performance for build_list_dir_cmd calls in browse.lua --- lua/async-remote-write/browse.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lua/async-remote-write/browse.lua b/lua/async-remote-write/browse.lua index c32a073..d3a151c 100644 --- a/lua/async-remote-write/browse.lua +++ b/lua/async-remote-write/browse.lua @@ -552,7 +552,7 @@ function M.browse_remote_directory(url, reset_selections) end -- Use ssh_utils helper to build directory listing command - local sh_cmd = ssh_utils.build_list_dir_cmd(path) + local sh_cmd = ssh_utils.build_list_dir_cmd(path, { sorted = false }) local cmd = { "ssh", host, sh_cmd } @@ -1559,7 +1559,7 @@ function M.load_directory_for_tree(url, depth, callback) end -- Use ssh_utils helper to build directory listing command - local ssh_cmd = ssh_utils.build_list_dir_cmd(path) + local ssh_cmd = ssh_utils.build_list_dir_cmd(path, { sorted = false }) local output = {} local stderr_output = {} @@ -3982,7 +3982,7 @@ function M.load_directory_v2(url, callback) end -- Use ssh_utils helper to build directory listing command - local ssh_cmd = ssh_utils.build_list_dir_cmd(path) + local ssh_cmd = ssh_utils.build_list_dir_cmd(path, { sorted = false }) local output = {} local stderr_output = {}