|
| 1 | +-- Unicode Escape Converter for Neovim |
| 2 | +-- Place this in your Neovim configuration (e.g., ~/.config/nvim/lua/unicode_escape.lua) |
| 3 | +-- Then require it in your init.lua with: require('unicode_escape') |
| 4 | + |
| 5 | +local M = {} |
| 6 | + |
| 7 | +-- Function to convert a Unicode code point to UTF-8 |
| 8 | +local function codepoint_to_utf8(n) |
| 9 | + if n <= 0x7f then |
| 10 | + return string.char(n) |
| 11 | + elseif n <= 0x7ff then |
| 12 | + return string.char( |
| 13 | + bit.bor(0xc0, bit.rshift(n, 6)), |
| 14 | + bit.bor(0x80, bit.band(n, 0x3f)) |
| 15 | + ) |
| 16 | + elseif n <= 0xffff then |
| 17 | + return string.char( |
| 18 | + bit.bor(0xe0, bit.rshift(n, 12)), |
| 19 | + bit.bor(0x80, bit.band(bit.rshift(n, 6), 0x3f)), |
| 20 | + bit.bor(0x80, bit.band(n, 0x3f)) |
| 21 | + ) |
| 22 | + elseif n <= 0x10ffff then |
| 23 | + return string.char( |
| 24 | + bit.bor(0xf0, bit.rshift(n, 18)), |
| 25 | + bit.bor(0x80, bit.band(bit.rshift(n, 12), 0x3f)), |
| 26 | + bit.bor(0x80, bit.band(bit.rshift(n, 6), 0x3f)), |
| 27 | + bit.bor(0x80, bit.band(n, 0x3f)) |
| 28 | + ) |
| 29 | + end |
| 30 | + return nil |
| 31 | +end |
| 32 | + |
| 33 | +-- Main function to convert Unicode escapes in visual selection |
| 34 | +function M.convert_unicode_escapes() |
| 35 | + -- Get the visual selection |
| 36 | + local start_line, start_col = unpack(vim.fn.getpos("'<"), 2, 3) |
| 37 | + local end_line, end_col = unpack(vim.fn.getpos("'>"), 2, 3) |
| 38 | + |
| 39 | + -- Get the lines |
| 40 | + local lines = vim.api.nvim_buf_get_lines(0, start_line - 1, end_line, false) |
| 41 | + |
| 42 | + if #lines == 0 then |
| 43 | + vim.notify("No text selected", vim.log.levels.WARN) |
| 44 | + return |
| 45 | + end |
| 46 | + |
| 47 | + local text |
| 48 | + if #lines == 1 then |
| 49 | + -- Single line selection |
| 50 | + text = string.sub(lines[1], start_col, end_col) |
| 51 | + else |
| 52 | + -- Multi-line selection |
| 53 | + local parts = {} |
| 54 | + parts[1] = string.sub(lines[1], start_col) |
| 55 | + for i = 2, #lines - 1 do |
| 56 | + parts[i] = lines[i] |
| 57 | + end |
| 58 | + parts[#lines] = string.sub(lines[#lines], 1, end_col) |
| 59 | + text = table.concat(parts, "\n") |
| 60 | + end |
| 61 | + |
| 62 | + -- Convert Unicode escapes |
| 63 | + local converted = text:gsub("\\u(%x%x%x%x)", function(hex) |
| 64 | + local codepoint = tonumber(hex, 16) |
| 65 | + if codepoint then |
| 66 | + local utf8 = codepoint_to_utf8(codepoint) |
| 67 | + if utf8 then |
| 68 | + return utf8 |
| 69 | + end |
| 70 | + end |
| 71 | + return "\\u" .. hex -- Return original if conversion fails |
| 72 | + end) |
| 73 | + |
| 74 | + -- Also handle basic escape sequences |
| 75 | + converted = converted:gsub("\\n", "\n") |
| 76 | + converted = converted:gsub("\\t", "\t") |
| 77 | + converted = converted:gsub("\\r", "\r") |
| 78 | + converted = converted:gsub('\\"', '"') |
| 79 | + converted = converted:gsub("\\'", "'") |
| 80 | + converted = converted:gsub("\\\\", "\\") |
| 81 | + |
| 82 | + -- Replace the selection |
| 83 | + if #lines == 1 then |
| 84 | + -- Single line replacement |
| 85 | + local new_line = string.sub(lines[1], 1, start_col - 1) .. converted .. string.sub(lines[1], end_col + 1) |
| 86 | + vim.api.nvim_buf_set_lines(0, start_line - 1, start_line, false, { new_line }) |
| 87 | + else |
| 88 | + -- Multi-line replacement |
| 89 | + local new_lines = vim.split(converted, "\n", { plain = true }) |
| 90 | + |
| 91 | + -- Adjust first line |
| 92 | + new_lines[1] = string.sub(lines[1], 1, start_col - 1) .. new_lines[1] |
| 93 | + |
| 94 | + -- Adjust last line |
| 95 | + if #new_lines == #lines then |
| 96 | + new_lines[#new_lines] = new_lines[#new_lines] .. string.sub(lines[#lines], end_col + 1) |
| 97 | + else |
| 98 | + -- Handle case where number of lines changed |
| 99 | + local last_original = string.sub(lines[#lines], end_col + 1) |
| 100 | + if #new_lines > 0 then |
| 101 | + new_lines[#new_lines] = new_lines[#new_lines] .. last_original |
| 102 | + else |
| 103 | + new_lines = { lines[1]:sub(1, start_col - 1) .. last_original } |
| 104 | + end |
| 105 | + end |
| 106 | + |
| 107 | + vim.api.nvim_buf_set_lines(0, start_line - 1, end_line, false, new_lines) |
| 108 | + end |
| 109 | + |
| 110 | + vim.notify("Unicode escapes converted", vim.log.levels.INFO) |
| 111 | +end |
| 112 | + |
| 113 | +-- Alternative implementation using vim.fn.nr2char for Neovim without bit library |
| 114 | +function M.convert_unicode_escapes_alt() |
| 115 | + -- Store the current register |
| 116 | + local save_reg = vim.fn.getreg('"') |
| 117 | + local save_regtype = vim.fn.getregtype('"') |
| 118 | + |
| 119 | + -- Yank the visual selection |
| 120 | + vim.cmd('normal! "vy') |
| 121 | + local text = vim.fn.getreg('v') |
| 122 | + |
| 123 | + -- Convert Unicode escapes using vim's nr2char function |
| 124 | + local converted = text:gsub("\\u(%x%x%x%x)", function(hex) |
| 125 | + local codepoint = tonumber(hex, 16) |
| 126 | + if codepoint then |
| 127 | + return vim.fn.nr2char(codepoint) |
| 128 | + end |
| 129 | + return "\\u" .. hex |
| 130 | + end) |
| 131 | + |
| 132 | + -- Handle basic escape sequences |
| 133 | + converted = converted:gsub("\\n", "\n") |
| 134 | + converted = converted:gsub("\\t", "\t") |
| 135 | + converted = converted:gsub("\\r", "\r") |
| 136 | + converted = converted:gsub('\\"', '"') |
| 137 | + converted = converted:gsub("\\'", "'") |
| 138 | + converted = converted:gsub("\\\\", "\\") |
| 139 | + |
| 140 | + -- Put the converted text back |
| 141 | + vim.fn.setreg('v', converted) |
| 142 | + vim.cmd('normal! gv"vp') |
| 143 | + |
| 144 | + -- Restore the register |
| 145 | + vim.fn.setreg('"', save_reg, save_regtype) |
| 146 | + |
| 147 | + vim.notify("Unicode escapes converted", vim.log.levels.INFO) |
| 148 | +end |
| 149 | + |
| 150 | +-- Setup function to create the mapping |
| 151 | +function M.setup() |
| 152 | + -- Main mapping using the alternative method (more reliable) |
| 153 | + vim.keymap.set('v', '<leader>u', function() |
| 154 | + M.convert_unicode_escapes_alt() |
| 155 | + end, { desc = 'Convert Unicode escape sequences to text' }) |
| 156 | + |
| 157 | + -- Secondary mapping for testing the direct method |
| 158 | + vim.keymap.set('v', '<leader>U', function() |
| 159 | + M.convert_unicode_escapes() |
| 160 | + end, { desc = 'Convert Unicode escapes (alternative method)' }) |
| 161 | + |
| 162 | + -- Create a command as well |
| 163 | + vim.api.nvim_create_user_command('UnicodeDecodeSelection', function() |
| 164 | + M.convert_unicode_escapes_alt() |
| 165 | + end, { range = true, desc = 'Decode Unicode escape sequences in selection' }) |
| 166 | +end |
| 167 | + |
| 168 | +-- Auto-setup when module is required |
| 169 | +M.setup() |
| 170 | + |
| 171 | +return M |
0 commit comments