From f3546546409512a47a8e5523b4700d4cf50b8659 Mon Sep 17 00:00:00 2001 From: Cristhian Melo Date: Sat, 6 Jul 2024 23:20:54 -0500 Subject: [PATCH 1/3] refactor(*): separate code --- lua/template-string.lua | 102 ++------------------------------ lua/utils/allowed_filetypes.lua | 17 ++++++ lua/utils/debounce.lua | 20 +++++++ lua/utils/react_utils.lua | 29 +++++++++ lua/utils/replace_quotes.lua | 59 ++++++++++++++++++ 5 files changed, 129 insertions(+), 98 deletions(-) create mode 100644 lua/utils/allowed_filetypes.lua create mode 100644 lua/utils/debounce.lua create mode 100644 lua/utils/react_utils.lua create mode 100644 lua/utils/replace_quotes.lua diff --git a/lua/template-string.lua b/lua/template-string.lua index b174974..f1c6b0a 100644 --- a/lua/template-string.lua +++ b/lua/template-string.lua @@ -1,105 +1,11 @@ ----@class TemplateStringConfig ----@field jsx_brackets boolean | nil - local M = {} -local allowed_filetypes = { - javascript = true, - typescript = true, - javascriptreact = true, - typescriptreact = true, -} - -local function is_allowed_filetype() - local filetype = vim.bo.filetype - return allowed_filetypes[filetype] or false -end - --- Function to wrap with {``} if inside a JSX/TSX component -local function wrap_with_brackets_if_necessary(content) - if content:find("%${") then - return "={`" .. content .. "`}" - else - return '="' .. content .. '"' - end -end - --- Function to replace quotes in the current line -local function replace_quotes_in_line() - if not is_allowed_filetype() then - return - end - - local row = vim.api.nvim_win_get_cursor(0)[1] - local line = vim.api.nvim_buf_get_lines(0, row - 1, row, false)[1] - - if not line then - return - end - - local new_line = line - - -- Replace quotes with backticks when ${ is found - new_line = new_line:gsub("(['\"])(.-)%${(.-)}(.-)%1", function(quote, before, inside, after) - return "`" .. before .. "${" .. inside .. "}" .. after .. "`" - end) - - if M.jsx_brackets then - -- Wrap with {``} if inside a JSX/TSX component - new_line = new_line:gsub("=%s*`([^`]*)`", wrap_with_brackets_if_necessary) - - -- Revert backticks to original quotes if ${ is not found - new_line = new_line:gsub("={[`{]+([^`]*)[`}]+}", function(content) - if not content:find("%${") then - -- Determine the original type of quotes, double or single - local original_quote = line:match("=[\"']") and '"' or line:match("=['\"]") and "'" or '"' - return "=" .. original_quote .. content .. original_quote - end - return "={" .. "`" .. content .. "`" .. "}" - end) - end - - -- Also handle reverting solitary backticks on normal lines - new_line = new_line:gsub("`([^`]*)`", function(content) - if not content:find("%${") then - -- Determine the original type of quotes, double or single - local original_quote = line:match("[\"']") or '"' - return original_quote .. content .. original_quote - end - return "`" .. content .. "`" - end) - - if new_line ~= line then - vim.api.nvim_buf_set_lines(0, row - 1, row, false, { new_line }) - end -end - --- Function to execute update with debounce -local function debounce(fn, ms) - local timer = vim.loop.new_timer() - return function(...) - timer:stop() - local argv = { ... } - timer:start( - ms, - 0, - vim.schedule_wrap(function() - fn(unpack(argv)) - end) - ) - end -end +local debounce = require("utils.debounce") +local quotes = require("utils.replace_quotes") --- Configures the plugin behavior. ----@param opts TemplateStringConfig | nil Optional plugin configuration. -function M.setup(opts) - opts = opts or {} - -- Enable brackets for JSX/TSX - local jsx_brackets = opts.jsx_brackets == nil or opts.jsx_brackets - M.jsx_brackets = jsx_brackets - - -- Enable debounce - local debounced_replace = debounce(replace_quotes_in_line, 100) +function M.setup() + local debounced_replace = debounce.debounce(quotes.replace_quotes_in_line, 100) vim.api.nvim_create_autocmd({ "TextChanged", "TextChangedI" }, { callback = debounced_replace, }) diff --git a/lua/utils/allowed_filetypes.lua b/lua/utils/allowed_filetypes.lua new file mode 100644 index 0000000..226b513 --- /dev/null +++ b/lua/utils/allowed_filetypes.lua @@ -0,0 +1,17 @@ +local vim = vim + +local M = {} + +local allowed_filetypes = { + javascript = true, + typescript = true, + javascriptreact = true, + typescriptreact = true, +} + +function M.is_allowed_filetype() + local filetype = vim.bo.filetype + return allowed_filetypes[filetype] or false +end + +return M diff --git a/lua/utils/debounce.lua b/lua/utils/debounce.lua new file mode 100644 index 0000000..3dfde1f --- /dev/null +++ b/lua/utils/debounce.lua @@ -0,0 +1,20 @@ +local vim = vim + +local M = {} + +function M.debounce(fn, ms) + local timer = vim.loop.new_timer() + return function(...) + timer:stop() + local argv = { ... } + timer:start( + ms, + 0, + vim.schedule_wrap(function() + fn(unpack(argv)) + end) + ) + end +end + +return M diff --git a/lua/utils/react_utils.lua b/lua/utils/react_utils.lua new file mode 100644 index 0000000..56b426f --- /dev/null +++ b/lua/utils/react_utils.lua @@ -0,0 +1,29 @@ +local ts = require("nvim-treesitter.ts_utils") + +local M = {} + +-- Función para verificar si el cursor está dentro de una prop de React +function M.is_inside_react_opening_element() + local node = ts.get_node_at_cursor() + + if not node then + return false + end + + -- Verificar si el nodo es un elemento de apertura JSX/TSX or prop + if node:type() == "string" or node:type() == "template_string" then + local prev_node = ts.get_previous_node(node) + + if not prev_node then + return false + end + + if prev_node:type() == "property_identifier" then + return true + end + end + + return false +end + +return M diff --git a/lua/utils/replace_quotes.lua b/lua/utils/replace_quotes.lua new file mode 100644 index 0000000..e3a0da9 --- /dev/null +++ b/lua/utils/replace_quotes.lua @@ -0,0 +1,59 @@ +local filetypes = require("utils.allowed_filetypes") +local u = require("utils.react_utils") +local M = {} + +-- Reemplazar comillas por brackets y backticks cuando se encuentran "${}" en el valor de una prop. Ex. propName={'hello ${value}'} +local function replace_quotes_with_backticks_and_brackets(line) + -- Reemplazar comillas por backticks y brackets + line = line:gsub("(['\"])(.-)%${(.-)}(.-)%1", function(_, before, inside, after) + return "{`" .. before .. "${" .. inside .. "}" .. after .. "`}" + end) + + -- Also handle reverting solitary backticks on normal lines + line = line:gsub("`([^`]*)`", function(content) + if not content:find("%${") then + local original_quote = line:match("[\"']") or '"' + return original_quote .. content .. original_quote + end + return "`" .. content .. "`" + end) + + return line +end + +function M.replace_quotes_in_line() + if not filetypes.is_allowed_filetype() then + return + end + + local row, col = unpack(vim.api.nvim_win_get_cursor(0)) + + local line = vim.api.nvim_buf_get_lines(0, row - 1, row, false)[1] + local new_line = line + + -- Check if inside a JSX/TSX element or opening element + if u.is_inside_react_opening_element() then + new_line = replace_quotes_with_backticks_and_brackets(new_line) + else + -- Replace quotes with backticks only when ${} is found + new_line = new_line:gsub("(['\"])(.-)%${(.-)}(.-)%1", function(_, before, inside, after) + return "`" .. before .. "${" .. inside .. "}" .. after .. "`" + end) + + -- Also handle reverting solitary backticks on normal lines + new_line = new_line:gsub("`([^`]*)`", function(content) + if not content:find("%${") then + local original_quote = line:match("[\"']") or '"' + return original_quote .. content .. original_quote + end + return "`" .. content .. "`" + end) + end + + -- Update the buffer if there were changes + if new_line ~= line then + vim.api.nvim_buf_set_lines(0, row - 1, row, false, { new_line }) + end +end + +return M From 1d7981be5dc158c3dd337796b71e8df7f7843ed1 Mon Sep 17 00:00:00 2001 From: Cristhian Melo Date: Sun, 7 Jul 2024 11:31:47 -0500 Subject: [PATCH 2/3] feat: refactor all code and enable auto setup --- lua/template-string.lua | 10 +++- .../allowed_filetypes.lua | 3 + lua/template-string/debounce.lua | 26 ++++++++ .../react_utils.lua | 6 +- lua/template-string/replace_quotes.lua | 56 ++++++++++++++++++ lua/utils/debounce.lua | 20 ------- lua/utils/replace_quotes.lua | 59 ------------------- plugin/template-string.vim | 7 +++ 8 files changed, 103 insertions(+), 84 deletions(-) rename lua/{utils => template-string}/allowed_filetypes.lua (59%) create mode 100644 lua/template-string/debounce.lua rename lua/{utils => template-string}/react_utils.lua (61%) create mode 100644 lua/template-string/replace_quotes.lua delete mode 100644 lua/utils/debounce.lua delete mode 100644 lua/utils/replace_quotes.lua create mode 100644 plugin/template-string.vim diff --git a/lua/template-string.lua b/lua/template-string.lua index f1c6b0a..ed44eee 100644 --- a/lua/template-string.lua +++ b/lua/template-string.lua @@ -1,14 +1,18 @@ local M = {} -local debounce = require("utils.debounce") -local quotes = require("utils.replace_quotes") +local debounce = require("template-string.debounce") +local quotes = require("template-string.replace_quotes") ---- Configures the plugin behavior. +--- Configures the behavior of the plugin. function M.setup() + -- Create a debounced version of the replace_quotes_in_line function local debounced_replace = debounce.debounce(quotes.replace_quotes_in_line, 100) + + -- Set up an autocmd to trigger the debounced function on TextChanged and TextChangedI events vim.api.nvim_create_autocmd({ "TextChanged", "TextChangedI" }, { callback = debounced_replace, }) end +--- @export return M diff --git a/lua/utils/allowed_filetypes.lua b/lua/template-string/allowed_filetypes.lua similarity index 59% rename from lua/utils/allowed_filetypes.lua rename to lua/template-string/allowed_filetypes.lua index 226b513..82162f9 100644 --- a/lua/utils/allowed_filetypes.lua +++ b/lua/template-string/allowed_filetypes.lua @@ -2,6 +2,7 @@ local vim = vim local M = {} +-- List of allowed file types for syntax checking local allowed_filetypes = { javascript = true, typescript = true, @@ -9,6 +10,8 @@ local allowed_filetypes = { typescriptreact = true, } +--- Checks if the current buffer's file type is allowed for syntax checking. +-- @return boolean indicating if the file type is allowed. function M.is_allowed_filetype() local filetype = vim.bo.filetype return allowed_filetypes[filetype] or false diff --git a/lua/template-string/debounce.lua b/lua/template-string/debounce.lua new file mode 100644 index 0000000..c4af56f --- /dev/null +++ b/lua/template-string/debounce.lua @@ -0,0 +1,26 @@ +local vim = vim + +local M = {} + +--- Creates a debounced version of a function. +-- @param fn The function to debounce. +-- @param ms The debounce time in milliseconds. +-- @return The debounced function. +function M.debounce(fn, ms) + local timer = vim.loop.new_timer() -- Create a new timer from Vim's event loop + + return function(...) + timer:stop() -- Stop the timer to reset debounce time + local argv = { ... } -- Capture arguments passed to the debounced function + + timer:start( + ms, + 0, + vim.schedule_wrap(function() + fn(unpack(argv)) -- Call the original function after debounce time + end) + ) + end +end + +return M diff --git a/lua/utils/react_utils.lua b/lua/template-string/react_utils.lua similarity index 61% rename from lua/utils/react_utils.lua rename to lua/template-string/react_utils.lua index 56b426f..75d1258 100644 --- a/lua/utils/react_utils.lua +++ b/lua/template-string/react_utils.lua @@ -2,7 +2,8 @@ local ts = require("nvim-treesitter.ts_utils") local M = {} --- Función para verificar si el cursor está dentro de una prop de React +--- Checks if the cursor is inside a React prop or JSX/TSX opening element. +-- @return boolean indicating if the cursor is inside a React prop or JSX/TSX opening element. function M.is_inside_react_opening_element() local node = ts.get_node_at_cursor() @@ -10,7 +11,7 @@ function M.is_inside_react_opening_element() return false end - -- Verificar si el nodo es un elemento de apertura JSX/TSX or prop + -- Check if the node is a JSX/TSX string or template string if node:type() == "string" or node:type() == "template_string" then local prev_node = ts.get_previous_node(node) @@ -18,6 +19,7 @@ function M.is_inside_react_opening_element() return false end + -- Check if the previous node is a property identifier if prev_node:type() == "property_identifier" then return true end diff --git a/lua/template-string/replace_quotes.lua b/lua/template-string/replace_quotes.lua new file mode 100644 index 0000000..bded6cd --- /dev/null +++ b/lua/template-string/replace_quotes.lua @@ -0,0 +1,56 @@ +local filetypes = require("template-string.allowed_filetypes") +local u = require("template-string.react_utils") +local M = {} + +--- Replaces quotes in a line based on the presence of template literals and context. +-- @param line The line of text to process. +-- @param use_brackets A boolean indicating if brackets should be used. +-- @return The processed line with appropriate quote replacements. +local function replace_quotes(line, use_brackets) + -- Replace quotes with backticks and brackets if ${} is found and use_brackets is true + line = line:gsub("(['\"])(.-)(%${.-})(.-)%1", function(quote, before, inside, after) + if use_brackets then + return "{`" .. before .. inside .. after .. "`}" + else + return "`" .. before .. inside .. after .. "`" + end + end) + + -- Revert backticks and brackets to original quotes if ${} is not found in the content + line = line:gsub("{`([^`]*)`}", function(content) + if not content:find("%${.-}") then + local original_quote = line:match("[\"']") or '"' + return original_quote .. content .. original_quote + end + return "{`" .. content .. "`}" + end) + + -- Revert solitary backticks to original quotes if ${} is not found in the content + line = line:gsub("`([^`]*)`", function(content) + if not content:find("%${.-}") then + local original_quote = line:match("[\"']") or '"' + return original_quote .. content .. original_quote + end + return "`" .. content .. "`" + end) + + return line +end + +--- Replaces quotes in the current line of the buffer if the filetype is allowed. +function M.replace_quotes_in_line() + if not filetypes.is_allowed_filetype() then + return + end + + local row = vim.api.nvim_win_get_cursor(0)[1] + local line = vim.api.nvim_buf_get_lines(0, row - 1, row, false)[1] + local new_line = replace_quotes(line, u.is_inside_react_opening_element()) + + -- Update the buffer if there were changes + if new_line ~= line then + vim.api.nvim_buf_set_lines(0, row - 1, row, false, { new_line }) + end +end + +return M diff --git a/lua/utils/debounce.lua b/lua/utils/debounce.lua deleted file mode 100644 index 3dfde1f..0000000 --- a/lua/utils/debounce.lua +++ /dev/null @@ -1,20 +0,0 @@ -local vim = vim - -local M = {} - -function M.debounce(fn, ms) - local timer = vim.loop.new_timer() - return function(...) - timer:stop() - local argv = { ... } - timer:start( - ms, - 0, - vim.schedule_wrap(function() - fn(unpack(argv)) - end) - ) - end -end - -return M diff --git a/lua/utils/replace_quotes.lua b/lua/utils/replace_quotes.lua deleted file mode 100644 index e3a0da9..0000000 --- a/lua/utils/replace_quotes.lua +++ /dev/null @@ -1,59 +0,0 @@ -local filetypes = require("utils.allowed_filetypes") -local u = require("utils.react_utils") -local M = {} - --- Reemplazar comillas por brackets y backticks cuando se encuentran "${}" en el valor de una prop. Ex. propName={'hello ${value}'} -local function replace_quotes_with_backticks_and_brackets(line) - -- Reemplazar comillas por backticks y brackets - line = line:gsub("(['\"])(.-)%${(.-)}(.-)%1", function(_, before, inside, after) - return "{`" .. before .. "${" .. inside .. "}" .. after .. "`}" - end) - - -- Also handle reverting solitary backticks on normal lines - line = line:gsub("`([^`]*)`", function(content) - if not content:find("%${") then - local original_quote = line:match("[\"']") or '"' - return original_quote .. content .. original_quote - end - return "`" .. content .. "`" - end) - - return line -end - -function M.replace_quotes_in_line() - if not filetypes.is_allowed_filetype() then - return - end - - local row, col = unpack(vim.api.nvim_win_get_cursor(0)) - - local line = vim.api.nvim_buf_get_lines(0, row - 1, row, false)[1] - local new_line = line - - -- Check if inside a JSX/TSX element or opening element - if u.is_inside_react_opening_element() then - new_line = replace_quotes_with_backticks_and_brackets(new_line) - else - -- Replace quotes with backticks only when ${} is found - new_line = new_line:gsub("(['\"])(.-)%${(.-)}(.-)%1", function(_, before, inside, after) - return "`" .. before .. "${" .. inside .. "}" .. after .. "`" - end) - - -- Also handle reverting solitary backticks on normal lines - new_line = new_line:gsub("`([^`]*)`", function(content) - if not content:find("%${") then - local original_quote = line:match("[\"']") or '"' - return original_quote .. content .. original_quote - end - return "`" .. content .. "`" - end) - end - - -- Update the buffer if there were changes - if new_line ~= line then - vim.api.nvim_buf_set_lines(0, row - 1, row, false, { new_line }) - end -end - -return M diff --git a/plugin/template-string.vim b/plugin/template-string.vim new file mode 100644 index 0000000..19cb8b1 --- /dev/null +++ b/plugin/template-string.vim @@ -0,0 +1,7 @@ +if exists('g:loaded_template_string') + finish +endif + +lua require'template-string'.setup() + +let g:loaded_template_string = 1 From 06a3e9745677e357d0ecd3a31c6b6b238b01968b Mon Sep 17 00:00:00 2001 From: Cristhian Melo Date: Sun, 7 Jul 2024 11:34:41 -0500 Subject: [PATCH 3/3] chore: update readme --- README.md | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index fe0f1a4..ba3d620 100644 --- a/README.md +++ b/README.md @@ -25,10 +25,8 @@ Install using your favorite package manager for Neovim. For example, using [lazy event = "BufReadPost", dependencies = { "nvim-lua/plenary.nvim", - }, - config = function() - require("template-string").setup() - end, + "nvim-treesitter/nvim-treesitter", + } } ``` @@ -53,19 +51,6 @@ const props = { ; ``` -## Configuration - -The `setup` function accepts an optional configuration object with the following options: - -- **jsx_brackets** `boolean | nil`: Enable/disable wrapping template literals with `{``}` inside JSX/TSX components. Defaults to `true` if not provided. - -```lua --- Default configuration -require('template-string').setup({ - jsx_brackets = true, -- Wrap template literals with {``} inside JSX/TSX components -}) -``` - ## License This plugin is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.