diff --git a/lua/hacker-helper.lua b/lua/hacker-helper.lua index a5a2308..8336f10 100644 --- a/lua/hacker-helper.lua +++ b/lua/hacker-helper.lua @@ -1,5 +1,9 @@ +-- utils +local selection_util = require("hacker-helper.selection_util") + -- main module file local module = require("hacker-helper.module") + -- Notify the user if LuaSocket is not installed local function check_luasocket_installed() local ok, mime = pcall(require, "mime") @@ -8,7 +12,10 @@ local function check_luasocket_installed() "Hacker Helper Error: LuaSocket (luasocket) is not installed. Please install it using LuaRocks: luarocks install luasocket", vim.log.levels.ERROR ) - vim.notify("Hacker Helper Error: sudo apt install luarocks", vim.log.levels.ERROR) + vim.notify( + "Hacker Helper Error: sudo apt install luarocks && sudo luarocks install luasocket", + vim.log.levels.ERROR + ) return nil end return mime @@ -31,6 +38,10 @@ local config = { opt = "Hello!", } +local mime = check_luasocket_installed() +if not mime then + return +end -- Ensure LuaSocket is installed ---@class MyModule local M = {} ---@type Config @@ -41,10 +52,6 @@ M.config = config ---@param user_config Config? User-provided configuration M.setup = function(user_config) -- Check if luasocket is installed - local mime = check_luasocket_installed() - if not mime then - return - end -- Ensure LuaSocket is installed -- Merge user configuration with defaults M.config = vim.tbl_deep_extend("force", M.config, user_config or {}) @@ -72,97 +79,75 @@ M.setup = function(user_config) { noremap = true, silent = true, desc = "Decoder" } ) - -- Key mappings for encoding and decoding - vim.keymap.set("v", M.config.prefix .. M.config.keys.encode_prefix .. M.config.keys.encode_url, function() - M.transform_selection("url", "encode") - end, { noremap = true, silent = true, desc = "URL Encode" }) + -- Key mappings for Base64 and URL encoding/decoding using config prefixes + + -- Base64 Encode vim.keymap.set("v", M.config.prefix .. M.config.keys.encode_prefix .. M.config.keys.encode_base64, function() - M.transform_selection("base64", "encode") + selection_util.transform_selection(function(text, selection_type) + return M.transform_func(text, selection_type, "encode", "base64") + end) end, { noremap = true, silent = true, desc = "Base64 Encode" }) - vim.keymap.set("v", M.config.prefix .. M.config.keys.decode_prefix .. M.config.keys.decode_url, function() - M.transform_selection("url", "decode") - end, { noremap = true, silent = true, desc = "URL Decode" }) + -- Base64 Decode vim.keymap.set("v", M.config.prefix .. M.config.keys.decode_prefix .. M.config.keys.decode_base64, function() - M.transform_selection("base64", "decode") + selection_util.transform_selection(function(text, selection_type) + return M.transform_func(text, selection_type, "decode", "base64") + end) end, { noremap = true, silent = true, desc = "Base64 Decode" }) + + -- URL Encode + vim.keymap.set("v", M.config.prefix .. M.config.keys.encode_prefix .. M.config.keys.encode_url, function() + selection_util.transform_selection(function(text, selection_type) + return M.transform_func(text, selection_type, "encode", "url") + end) + end, { noremap = true, silent = true, desc = "URL Encode" }) + + -- URL Decode + vim.keymap.set("v", M.config.prefix .. M.config.keys.decode_prefix .. M.config.keys.decode_url, function() + selection_util.transform_selection(function(text, selection_type) + return M.transform_func(text, selection_type, "decode", "url") + end) + end, { noremap = true, silent = true, desc = "URL Decode" }) end -- Function to handle encoding/decoding based on selection -M.transform_selection = function(type, mode) - local mime = check_luasocket_installed() - if not mime then - return - end -- Ensure LuaSocket is installed - - -- Capture the selection - local start_pos = vim.fn.getpos("'<") - local end_pos = vim.fn.getpos("'>") - - local start_line = start_pos[2] - local start_col = start_pos[3] - 1 -- 0-based index - - local end_line = end_pos[2] - local end_col = end_pos[3] -- inclusive - - -- Get selected lines - local lines = vim.fn.getline(start_line, end_line) - - -- Handle full-line selection (V mode) and partial selection - if vim.fn.mode() == "V" then - -- Full line selection, transform entire lines - for i, line in ipairs(lines) do - lines[i] = M.transform_text(line, type, mode, mime) - end - -- Replace the entire range with transformed lines - vim.fn.setline(start_line, lines) - else - -- Partial selection within a single line - if start_line == end_line then - -- Handle partial selection on a single line - local line = lines[1] - local selection = string.sub(line, start_col + 1, end_col) - local transformed = M.transform_text(selection, type, mode, mime) - local new_line = string.sub(line, 1, start_col) .. transformed .. string.sub(line, end_col + 1) - vim.fn.setline(start_line, new_line) - else - -- Handle multi-line selection, applying the transformation to partial lines - lines[1] = string.sub(lines[1], 1, start_col) - .. M.transform_text(string.sub(lines[1], start_col + 1), type, mode, mime) - lines[#lines] = M.transform_text(string.sub(lines[#lines], 1, end_col), type, mode, mime) - .. string.sub(lines[#lines], end_col + 1) - for i = 2, #lines - 1 do - lines[i] = M.transform_text(lines[i], type, mode, mime) - end - -- Replace the entire range with transformed lines - vim.fn.setline(start_line, lines) - end - end +-- Base64 encoding and decoding utility functions +M.base64_encode = function(text) + return mime.b64(text) end -M.transform_text = function(text, mode, type, mime) - mime = mime or require("mime") -- Ensure LuaSocket's mime module is loaded - - if type == "url" then - if mode == "encode" then - return text:gsub("([^%w%.%-_])", function(c) - return string.format("%%%02X", string.byte(c)) - end) - elseif mode == "decode" then - return text:gsub("%%(%x%x)", function(hex) - return string.char(tonumber(hex, 16)) - end) +M.base64_decode = function(text) + return mime.unb64(text) +end + +-- URL encoding and decoding utility functions +M.url_encode = function(text) + return text:gsub("([^%w%.%-_])", function(c) + return string.format("%%%02X", string.byte(c)) + end) +end + +M.url_decode = function(text) + return text:gsub("%%(%x%x)", function(hex) + return string.char(tonumber(hex, 16)) + end) +end + +-- Transform function for encoding or decoding text based on type and selection type +M.transform_func = function(text, selection_type, encode_or_decode, encoding_type) + if encoding_type == "base64" then + if encode_or_decode == "encode" then + return M.base64_encode(text) + elseif encode_or_decode == "decode" then + return M.base64_decode(text) end - elseif type == "base64" then - if mode == "encode" then - local encoded = mime.b64(text) -- Use only the first return value - return encoded - elseif mode == "decode" then - local decoded = mime.unb64(text) -- Use only the first return value - return decoded + elseif encoding_type == "url" then + if encode_or_decode == "encode" then + return M.url_encode(text) + elseif encode_or_decode == "decode" then + return M.url_decode(text) end end - - return text -- return the original text if no valid type or mode is provided + return text end M.hello = function() diff --git a/lua/hacker-helper/selection_util.lua b/lua/hacker-helper/selection_util.lua new file mode 100644 index 0000000..7bc4376 --- /dev/null +++ b/lua/hacker-helper/selection_util.lua @@ -0,0 +1,69 @@ +-- lua/hacker-helper/selection_util.lua + +local M = {} + +-- Utility function to capture the visual selection, apply a transformation, and replace the selection +M.transform_selection = function(transform_func) + -- Reselect the current visual block to ensure the latest selection is active + vim.cmd("normal! gv") + + -- Get the visual selection range using visual marks + local start_pos = vim.fn.getpos("'<") -- Start of the visual selection + local end_pos = vim.fn.getpos("'>") -- End of the visual selection + + -- Determine start and end lines and columns + local start_line = math.min(start_pos[2], end_pos[2]) + local end_line = math.max(start_pos[2], end_pos[2]) + local start_col = start_pos[3] - 1 -- 0-based index for inline selection + local end_col = end_pos[3] -- inclusive for inline selection + + -- Get the selected lines + local lines = vim.fn.getline(start_line, end_line) + + -- Ensure all values in the lines array are valid strings + for i = 1, #lines do + lines[i] = tostring(lines[i] or "") -- Ensure that each element is a string + end + + -- Handle visual line selection (V) and inline selection (v) + if vim.fn.visualmode() == "V" then + -- Full line selection + for i, line in ipairs(lines) do + lines[i] = transform_func(line, "full_line") + end + -- Replace the selected lines with the transformed text + vim.fn.setline(start_line, lines) + else + -- Inline selection (v mode) + if start_line == end_line then + -- Handle inline selection on a single line + local line = lines[1] or "" + -- Ensure start_col and end_col are valid + start_col = math.max(0, start_col) + end_col = math.min(#line, end_col) + -- Capture the selected part of the line + local selection = string.sub(line, start_col + 1, end_col) + -- Transform the selection and pass that it's a specific selection + local transformed = transform_func(selection or "", "specific_selection") + -- Replace the selected part with the transformed text + local new_line = string.sub(line, 1, start_col) .. transformed .. string.sub(line, end_col + 1) + vim.fn.setline(start_line, new_line) + else + -- Handle multi-line partial selection + local first_line = string.sub(lines[1] or "", start_col + 1) + local last_line = string.sub(lines[#lines] or "", 1, end_col) + lines[1] = string.sub(lines[1] or "", 1, start_col) .. transform_func(first_line or "", "multi_line") + lines[#lines] = transform_func(last_line or "", "multi_line") .. string.sub(lines[#lines] or "", end_col + 1) + for i = 2, #lines - 1 do + lines[i] = transform_func(lines[i] or "", "multi_line") + end + -- Replace the selected lines with the transformed text + vim.fn.setline(start_line, lines) + end + end + + -- Reselect the visual selection after transformation (optional) + vim.cmd("normal! gv") +end + +return M diff --git a/tests/hacker-helper/hacker_helper_spec.lua b/tests/hacker-helper/hacker_helper_spec.lua index f0a5bfc..3fa4d52 100644 --- a/tests/hacker-helper/hacker_helper_spec.lua +++ b/tests/hacker-helper/hacker_helper_spec.lua @@ -1,40 +1,67 @@ local plugin = require("hacker-helper") - -describe("setup", function() - it("works with default", function() - assert(plugin.hello() == "Hello!", "my first function with param = Hello!") - end) - - it("works with custom var", function() - plugin.setup({ opt = "custom" }) - assert(plugin.hello() == "custom", "my first function with param = custom") - end) -end) +-- todo: ensure that we test the setup +-- describe("setup", function() +-- it("works with default", function() +-- assert(plugin.hello() == "Hello!", "my first function with param = Hello!") +-- end) +-- +-- it("works with custom var", function() +-- plugin.setup({ opt = "custom" }) +-- assert(plugin.hello() == "custom", "my first function with param = custom") +-- end) +-- end) describe("Base64 and URL encoding/decoding", function() -- Test Base64 encoding and decoding it("encodes Base64 correctly", function() local text = "Hello, World!" - local encoded = plugin.transform_text(text, "encode", "base64") - assert(encoded == "SGVsbG8sIFdvcmxkIQ==", "Base64 encoding failed") + local encoded = plugin.transform_func(text, "specific_selection", "encode", "base64") + assert.are.equal("SGVsbG8sIFdvcmxkIQ==", encoded) end) it("decodes Base64 correctly", function() local encoded_text = "SGVsbG8sIFdvcmxkIQ==" - local decoded = plugin.transform_text(encoded_text, "decode", "base64") - assert(decoded == "Hello, World!", "Base64 decoding failed") + local decoded = plugin.transform_func(encoded_text, "specific_selection", "decode", "base64") + assert.are.equal("Hello, World!", decoded) end) -- Test URL encoding and decoding it("encodes URL correctly", function() local text = "Hello, World!" - local encoded = plugin.transform_text(text, "encode", "url") - assert(encoded == "Hello%2C%20World%21", "URL encoding failed") + local encoded = plugin.transform_func(text, "specific_selection", "encode", "url") + assert.are.equal("Hello%2C%20World%21", encoded) end) it("decodes URL correctly", function() local encoded_text = "Hello%2C%20World%21" - local decoded = plugin.transform_text(encoded_text, "decode", "url") - assert(decoded == "Hello, World!", "URL decoding failed") + local decoded = plugin.transform_func(encoded_text, "specific_selection", "decode", "url") + assert.are.equal("Hello, World!", decoded) + end) + + -- Test full-line and multi-line selections for Base64 + it("encodes a full line with Base64 correctly", function() + local text = "This is a full line." + local encoded = plugin.transform_func(text, "full_line", "encode", "base64") + assert.are.equal("VGhpcyBpcyBhIGZ1bGwgbGluZS4=", encoded) + end) + + it("decodes multi-line Base64 correctly", function() + local text = "VGhpcyBpcyBhIGZ1bGwgbGluZS4=" + local decoded = plugin.transform_func(text, "multi_line", "decode", "base64") + assert.are.equal("This is a full line.", decoded) + end) + + -- Test handling of unknown encoding type + it("returns the same text if encoding type is unknown", function() + local text = "Hello, World!" + local result = plugin.transform_func(text, "specific_selection", "encode", "unknown") + assert.are.equal(text, result) + end) + + -- Test handling of unknown action (not encode/decode) + it("returns the same text if the action is not encode or decode", function() + local text = "Hello, World!" + local result = plugin.transform_func(text, "specific_selection", "unknown_action", "base64") + assert.are.equal(text, result) end) end)