From 8cd12cea7dd11a5b3470428a7d5e6e989d558c6b Mon Sep 17 00:00:00 2001 From: Itzamna Date: Wed, 12 Feb 2025 20:38:18 -0600 Subject: [PATCH 1/3] Add picker support with fzf.nvim --- lua/fzf/remote-sshfs.lua | 258 ++++++++++++++++++++++++++++++++++++++ lua/remote-sshfs/init.lua | 20 ++- 2 files changed, 272 insertions(+), 6 deletions(-) create mode 100644 lua/fzf/remote-sshfs.lua diff --git a/lua/fzf/remote-sshfs.lua b/lua/fzf/remote-sshfs.lua new file mode 100644 index 0000000..f64bdbb --- /dev/null +++ b/lua/fzf/remote-sshfs.lua @@ -0,0 +1,258 @@ +local api = vim.api +local fn = vim.fn + +-- Build virtualized host file from parsed hosts from plugin +local function build_host_preview(hosts, name) + if name == "" or name == nil then + return {} + end + + local lines = {} + local host = hosts[name] + + table.insert(lines, "# Config: " .. host["Config"]) + table.insert(lines, "Host " .. host["Name"]) + for key, value in pairs(host) do + if key ~= "Name" and key ~= "Config" then + table.insert(lines, string.format("\t%s %s", key, value)) + end + end + table.insert(lines, "") + + return table.concat(lines, "\n") +end + +-- FZF action to select a host to connect to +local function connect(_) + local connections = require("remote-sshfs.connections") + local hosts = connections.list_hosts() + + -- Create a temporary file to store host previews + local preview_file = vim.fn.tempname() + local preview_data = {} + + -- Prepare preview data for each host + local source = {} + for hostname, _ in pairs(hosts) do + table.insert(source, hostname) + preview_data[hostname] = build_host_preview(hosts, hostname) + end + + -- Write preview data to temp file + local lines = {} + for hostname, preview in pairs(preview_data) do + table.insert(lines, hostname .. "\n" .. preview .. "\n---") + end + vim.fn.writefile(lines, preview_file) + + -- Create preview command that reads from the temp file + local preview_cmd = string.format( + 'awk -v RS="---" "/^%s\\n/{print}" %s | tail -n +2', + '{}', + preview_file + ) + + -- Build fzf options string + local opts = '--prompt="Connect to remote host> "' + .. ' --preview="' .. preview_cmd .. '"' + .. ' --preview-window="right:50%"' + + -- Create spec table for fzf + local spec = { + source = source, + sink = function(selected) + local host = hosts[selected] + connections.connect(host) + -- Clean up temp file + vim.fn.delete(preview_file) + end, + options = opts + } + + -- Run fzf with proper wrapping + vim.fn['fzf#run'](vim.fn['fzf#wrap']('remote-sshfs', spec, 0)) +end + +-- FZF action to select ssh config file to edit +local function edit_config(_) + local connections = require("remote-sshfs.connections") + local ssh_configs = connections.list_ssh_configs() + + vim.fn['fzf#run'](vim.fn['fzf#wrap']({ + source = ssh_configs, + sink = function(selected) + vim.cmd('edit ' .. selected) + end, + options = { + ['--prompt'] = 'Choose SSH config file to edit> ', + ['--preview'] = 'cat {}', + ['--preview-window'] = 'right:50%', + } + })) +end + +local function command_exists_on_remote(command, server) + local ssh_cmd = string.format('ssh %s "which %s"', server, command) + local result = vim.fn.system(ssh_cmd) + return result ~= "" +end + +-- Remote find_files implementation +local function find_files(opts) + opts = opts or {} + local connections = require("remote-sshfs.connections") + + if not connections.is_connected() then + vim.notify("You are not currently connected to a remote host.") + return + end + + local mount_point = opts.mount_point or connections.get_current_mount_point() + local current_host = connections.get_current_host() + + -- Build the find command + local find_command = (function() + if opts.find_command then + if type(opts.find_command) == "function" then + return opts.find_command(opts) + end + return opts.find_command + elseif command_exists_on_remote("rg", current_host["Name"]) then + return { "ssh", current_host["Name"], "-C", "rg", "--files", "--color", "never" } + elseif command_exists_on_remote("fd", current_host["Name"]) then + return { "ssh", current_host["Name"], "fd", "--type", "f", "--color", "never" } + elseif command_exists_on_remote("fdfind", current_host["Name"]) then + return { "ssh", current_host["Name"], "fdfind", "--type", "f", "--color", "never" } + elseif command_exists_on_remote("where", current_host["Name"]) then + return { "ssh", current_host["Name"], "where", "/r", ".", "*" } + end + end)() + + if not find_command then + vim.notify("Remote host does not support any available find commands (rg, fd, fdfind, where).") + return + end + + -- Adapt command based on options + local command = find_command[3] + if command == "fd" or command == "fdfind" or command == "rg" then + if opts.hidden then + table.insert(find_command, "--hidden") + end + if opts.no_ignore then + table.insert(find_command, "--no-ignore") + end + if opts.follow then + table.insert(find_command, "-L") + end + end + + -- Create FZF options + local fzf_opts = { + source = table.concat(find_command, " "), + sink = function(selected) + vim.cmd('edit ' .. mount_point .. '/' .. selected) + end, + options = { + ['--prompt'] = 'Remote Find Files> ', + ['--preview'] = string.format('cat %s/{}', mount_point), + ['--preview-window'] = 'right:50%', + } + } + + vim.fn['fzf#run'](vim.fn['fzf#wrap'](fzf_opts)) +end + +-- Remote live_grep implementation +local function live_grep(opts) + opts = opts or {} + local connections = require("remote-sshfs.connections") + + if not connections.is_connected() then + vim.notify("You are not currently connected to a remote host.") + return + end + + local current_host = connections.get_current_host() + local mount_point = opts.mount_point or connections.get_current_mount_point() + + -- Build the rg command + local rg_command = { + "ssh", + current_host["Name"], + "-C", + "rg", + "--column", + "--line-number", + "--no-heading", + "--color=always", + "--smart-case", + } + + if opts.type_filter then + table.insert(rg_command, "--type=" .. opts.type_filter) + end + + if opts.glob_pattern then + if type(opts.glob_pattern) == "string" then + table.insert(rg_command, "--glob=" .. opts.glob_pattern) + elseif type(opts.glob_pattern) == "table" then + for _, pattern in ipairs(opts.glob_pattern) do + table.insert(rg_command, "--glob=" .. pattern) + end + end + end + + -- Create FZF options + local fzf_opts = { + source = table.concat(rg_command, " "), + sink = function(selected) + -- Parse the selection (format: file:line:col:text) + local parts = vim.split(selected, ":") + local file = parts[1] + local line = tonumber(parts[2]) + local col = tonumber(parts[3]) + + -- Open the file at the specific location + vim.cmd('edit ' .. mount_point .. '/' .. file) + vim.api.nvim_win_set_cursor(0, {line, col - 1}) + end, + options = { + ['--prompt'] = 'Remote Live Grep> ', + ['--preview'] = string.format('bat --style=numbers --color=always %s/$(echo {} | cut -d: -f1)', mount_point), + ['--preview-window'] = 'right:50%', + ['--delimiter'] = ':', + ['--nth'] = '4..', + } + } + + vim.fn['fzf#run'](vim.fn['fzf#wrap'](fzf_opts)) +end + +-- Initialize plugin +local function setup() + -- Create user commands + vim.api.nvim_create_user_command('RemoteConnect', function(opts) + connect(opts) + end, {}) + + vim.api.nvim_create_user_command('RemoteEdit', function(opts) + edit_config(opts) + end, {}) + + vim.api.nvim_create_user_command('RemoteFiles', function(opts) + find_files(opts) + end, {}) + + vim.api.nvim_create_user_command('RemoteGrep', function(opts) + live_grep(opts) + end, {}) +end + +return { + setup = setup, + connect = connect, + edit_config = edit_config, + find_files = find_files, + live_grep = live_grep, +} diff --git a/lua/remote-sshfs/init.lua b/lua/remote-sshfs/init.lua index 8788759..9ca0a80 100644 --- a/lua/remote-sshfs/init.lua +++ b/lua/remote-sshfs/init.lua @@ -43,20 +43,28 @@ local default_opts = { sshfs = false, }, }, + picker = "fzf" } -M.setup_commands = function() +M.setup_commands = function(config) + local picker + if config.picker == "fzf" then + picker = require("../fzf/remote-sshfs") + else + picker = require("telescope").extensions["remote-sshfs"] + end + -- Create commands to connect/edit/reload/disconnect/find_files/live_grep vim.api.nvim_create_user_command("RemoteSSHFSConnect", function(opts) if opts.args and opts.args ~= "" then local host = require("remote-sshfs.utils").parse_host_from_command(opts.args) require("remote-sshfs.connections").connect(host) else - require("telescope").extensions["remote-sshfs"].connect() + picker.connect() end end, { nargs = "?", desc = "Remotely connect to host via picker or command as argument." }) vim.api.nvim_create_user_command("RemoteSSHFSEdit", function() - require("telescope").extensions["remote-sshfs"].edit() + picker.edit() end, {}) vim.api.nvim_create_user_command("RemoteSSHFSReload", function() require("remote-sshfs.connections").reload() @@ -65,10 +73,10 @@ M.setup_commands = function() require("remote-sshfs.connections").unmount_host() end, {}) vim.api.nvim_create_user_command("RemoteSSHFSFindFiles", function() - require("telescope").extensions["remote-sshfs"].find_files {} + picker.find_files {} end, {}) vim.api.nvim_create_user_command("RemoteSSHFSLiveGrep", function() - require("telescope").extensions["remote-sshfs"].live_grep {} + picker.live_grep {} end, {}) end @@ -80,7 +88,7 @@ M.setup = function(config) require("remote-sshfs.handler").setup(opts) require("remote-sshfs.log").setup(opts) - M.setup_commands() + M.setup_commands(opts) end return M From c71d854c3e72ca2bed9912a37971d345b72a20fc Mon Sep 17 00:00:00 2001 From: Itzamna Date: Wed, 12 Feb 2025 21:42:29 -0600 Subject: [PATCH 2/3] Fix find_files and add fix to live_grep --- lua/fzf/remote-sshfs.lua | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/lua/fzf/remote-sshfs.lua b/lua/fzf/remote-sshfs.lua index f64bdbb..99dfcea 100644 --- a/lua/fzf/remote-sshfs.lua +++ b/lua/fzf/remote-sshfs.lua @@ -147,20 +147,22 @@ local function find_files(opts) end end - -- Create FZF options - local fzf_opts = { + -- Build fzf options string + local opts = '--prompt="Remote Find Files> "' + .. ' --preview="cat ' .. mount_point .. '/{}"' + .. ' --preview-window="right:50%"' + + -- Create spec table for fzf + local spec = { source = table.concat(find_command, " "), sink = function(selected) vim.cmd('edit ' .. mount_point .. '/' .. selected) end, - options = { - ['--prompt'] = 'Remote Find Files> ', - ['--preview'] = string.format('cat %s/{}', mount_point), - ['--preview-window'] = 'right:50%', - } + options = opts } - vim.fn['fzf#run'](vim.fn['fzf#wrap'](fzf_opts)) + -- Run fzf with proper wrapping + vim.fn['fzf#run'](vim.fn['fzf#wrap']('remote-find', spec, 0)) end -- Remote live_grep implementation @@ -203,8 +205,15 @@ local function live_grep(opts) end end - -- Create FZF options - local fzf_opts = { + -- Build fzf options string + local opts = '--prompt="Remote Live Grep> "' + .. ' --preview="bat --style=numbers --color=always ' .. mount_point .. '/$(echo {} | cut -d: -f1)"' + .. ' --preview-window="right:50%"' + .. ' --delimiter=":"' + .. ' --nth="4.."' + + -- Create spec table for fzf + local spec = { source = table.concat(rg_command, " "), sink = function(selected) -- Parse the selection (format: file:line:col:text) @@ -217,16 +226,11 @@ local function live_grep(opts) vim.cmd('edit ' .. mount_point .. '/' .. file) vim.api.nvim_win_set_cursor(0, {line, col - 1}) end, - options = { - ['--prompt'] = 'Remote Live Grep> ', - ['--preview'] = string.format('bat --style=numbers --color=always %s/$(echo {} | cut -d: -f1)', mount_point), - ['--preview-window'] = 'right:50%', - ['--delimiter'] = ':', - ['--nth'] = '4..', - } + options = opts } - vim.fn['fzf#run'](vim.fn['fzf#wrap'](fzf_opts)) + -- Run fzf with proper wrapping + vim.fn['fzf#run'](vim.fn['fzf#wrap']('remote-grep', spec, 0)) end -- Initialize plugin From 38e9c7a91146aa9cfa5b39116a756ce10dd799f6 Mon Sep 17 00:00:00 2001 From: Tony Duco Date: Fri, 14 Mar 2025 12:10:20 -0400 Subject: [PATCH 3/3] chore: fix linting issues --- lua/fzf/remote-sshfs.lua | 84 ++++++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 41 deletions(-) diff --git a/lua/fzf/remote-sshfs.lua b/lua/fzf/remote-sshfs.lua index 99dfcea..ae9fdec 100644 --- a/lua/fzf/remote-sshfs.lua +++ b/lua/fzf/remote-sshfs.lua @@ -24,37 +24,35 @@ end -- FZF action to select a host to connect to local function connect(_) - local connections = require("remote-sshfs.connections") + local connections = require "remote-sshfs.connections" local hosts = connections.list_hosts() - + -- Create a temporary file to store host previews local preview_file = vim.fn.tempname() local preview_data = {} - + -- Prepare preview data for each host local source = {} for hostname, _ in pairs(hosts) do table.insert(source, hostname) preview_data[hostname] = build_host_preview(hosts, hostname) end - + -- Write preview data to temp file local lines = {} for hostname, preview in pairs(preview_data) do table.insert(lines, hostname .. "\n" .. preview .. "\n---") end vim.fn.writefile(lines, preview_file) - + -- Create preview command that reads from the temp file - local preview_cmd = string.format( - 'awk -v RS="---" "/^%s\\n/{print}" %s | tail -n +2', - '{}', - preview_file - ) - + local preview_cmd = string.format('awk -v RS="---" "/^%s\\n/{print}" %s | tail -n +2', "{}", preview_file) + -- Build fzf options string local opts = '--prompt="Connect to remote host> "' - .. ' --preview="' .. preview_cmd .. '"' + .. ' --preview="' + .. preview_cmd + .. '"' .. ' --preview-window="right:50%"' -- Create spec table for fzf @@ -66,29 +64,29 @@ local function connect(_) -- Clean up temp file vim.fn.delete(preview_file) end, - options = opts + options = opts, } -- Run fzf with proper wrapping - vim.fn['fzf#run'](vim.fn['fzf#wrap']('remote-sshfs', spec, 0)) + vim.fn["fzf#run"](vim.fn["fzf#wrap"]("remote-sshfs", spec, 0)) end -- FZF action to select ssh config file to edit local function edit_config(_) - local connections = require("remote-sshfs.connections") + local connections = require "remote-sshfs.connections" local ssh_configs = connections.list_ssh_configs() - vim.fn['fzf#run'](vim.fn['fzf#wrap']({ + vim.fn["fzf#run"](vim.fn["fzf#wrap"] { source = ssh_configs, sink = function(selected) - vim.cmd('edit ' .. selected) + vim.cmd("edit " .. selected) end, options = { - ['--prompt'] = 'Choose SSH config file to edit> ', - ['--preview'] = 'cat {}', - ['--preview-window'] = 'right:50%', - } - })) + ["--prompt"] = "Choose SSH config file to edit> ", + ["--preview"] = "cat {}", + ["--preview-window"] = "right:50%", + }, + }) end local function command_exists_on_remote(command, server) @@ -100,10 +98,10 @@ end -- Remote find_files implementation local function find_files(opts) opts = opts or {} - local connections = require("remote-sshfs.connections") + local connections = require "remote-sshfs.connections" if not connections.is_connected() then - vim.notify("You are not currently connected to a remote host.") + vim.notify "You are not currently connected to a remote host." return end @@ -129,7 +127,7 @@ local function find_files(opts) end)() if not find_command then - vim.notify("Remote host does not support any available find commands (rg, fd, fdfind, where).") + vim.notify "Remote host does not support any available find commands (rg, fd, fdfind, where)." return end @@ -149,29 +147,31 @@ local function find_files(opts) -- Build fzf options string local opts = '--prompt="Remote Find Files> "' - .. ' --preview="cat ' .. mount_point .. '/{}"' + .. ' --preview="cat ' + .. mount_point + .. '/{}"' .. ' --preview-window="right:50%"' -- Create spec table for fzf local spec = { source = table.concat(find_command, " "), sink = function(selected) - vim.cmd('edit ' .. mount_point .. '/' .. selected) + vim.cmd("edit " .. mount_point .. "/" .. selected) end, - options = opts + options = opts, } -- Run fzf with proper wrapping - vim.fn['fzf#run'](vim.fn['fzf#wrap']('remote-find', spec, 0)) + vim.fn["fzf#run"](vim.fn["fzf#wrap"]("remote-find", spec, 0)) end -- Remote live_grep implementation local function live_grep(opts) opts = opts or {} - local connections = require("remote-sshfs.connections") + local connections = require "remote-sshfs.connections" if not connections.is_connected() then - vim.notify("You are not currently connected to a remote host.") + vim.notify "You are not currently connected to a remote host." return end @@ -207,7 +207,9 @@ local function live_grep(opts) -- Build fzf options string local opts = '--prompt="Remote Live Grep> "' - .. ' --preview="bat --style=numbers --color=always ' .. mount_point .. '/$(echo {} | cut -d: -f1)"' + .. ' --preview="bat --style=numbers --color=always ' + .. mount_point + .. '/$(echo {} | cut -d: -f1)"' .. ' --preview-window="right:50%"' .. ' --delimiter=":"' .. ' --nth="4.."' @@ -221,34 +223,34 @@ local function live_grep(opts) local file = parts[1] local line = tonumber(parts[2]) local col = tonumber(parts[3]) - + -- Open the file at the specific location - vim.cmd('edit ' .. mount_point .. '/' .. file) - vim.api.nvim_win_set_cursor(0, {line, col - 1}) + vim.cmd("edit " .. mount_point .. "/" .. file) + vim.api.nvim_win_set_cursor(0, { line, col - 1 }) end, - options = opts + options = opts, } -- Run fzf with proper wrapping - vim.fn['fzf#run'](vim.fn['fzf#wrap']('remote-grep', spec, 0)) + vim.fn["fzf#run"](vim.fn["fzf#wrap"]("remote-grep", spec, 0)) end -- Initialize plugin local function setup() -- Create user commands - vim.api.nvim_create_user_command('RemoteConnect', function(opts) + vim.api.nvim_create_user_command("RemoteConnect", function(opts) connect(opts) end, {}) - vim.api.nvim_create_user_command('RemoteEdit', function(opts) + vim.api.nvim_create_user_command("RemoteEdit", function(opts) edit_config(opts) end, {}) - vim.api.nvim_create_user_command('RemoteFiles', function(opts) + vim.api.nvim_create_user_command("RemoteFiles", function(opts) find_files(opts) end, {}) - vim.api.nvim_create_user_command('RemoteGrep', function(opts) + vim.api.nvim_create_user_command("RemoteGrep", function(opts) live_grep(opts) end, {}) end