From ecd4d6bbbcd45231f2e8ea00961f076ff56dd1e7 Mon Sep 17 00:00:00 2001 From: Chen Asraf Date: Sun, 5 May 2024 22:53:49 +0300 Subject: [PATCH] fix: range commands --- doc/tags | 8 +-- doc/text-transform.txt | 16 +++--- lua/text-transform/commands.lua | 9 ++- lua/text-transform/replacers.lua | 62 +++++++++++++-------- lua/text-transform/state.lua | 86 +++++++++++++++++------------ lua/text-transform/transformers.lua | 6 +- lua/text-transform/util/debug.lua | 4 +- lua/text-transform/util/init.lua | 4 ++ 8 files changed, 117 insertions(+), 78 deletions(-) diff --git a/doc/tags b/doc/tags index af71ebf..f74aace 100644 --- a/doc/tags +++ b/doc/tags @@ -1,12 +1,9 @@ TextTransform.config text-transform.txt /*TextTransform.config* -TextTransform.enable() text-transform.txt /*TextTransform.enable()* TextTransform.get_visual_selection_details() text-transform.txt /*TextTransform.get_visual_selection_details()* TextTransform.init_commands() text-transform.txt /*TextTransform.init_commands()* TextTransform.replace_columns() text-transform.txt /*TextTransform.replace_columns()* TextTransform.replace_selection() text-transform.txt /*TextTransform.replace_selection()* TextTransform.replace_word() text-transform.txt /*TextTransform.replace_word()* -TextTransform.restore_positions() text-transform.txt /*TextTransform.restore_positions()* -TextTransform.save_positions() text-transform.txt /*TextTransform.save_positions()* TextTransform.setup() text-transform.txt /*TextTransform.setup()* TextTransform.to_camel_case() text-transform.txt /*TextTransform.to_camel_case()* TextTransform.to_const_case() text-transform.txt /*TextTransform.to_const_case()* @@ -16,10 +13,13 @@ TextTransform.to_pascal_case() text-transform.txt /*TextTransform.to_pascal_case TextTransform.to_snake_case() text-transform.txt /*TextTransform.to_snake_case()* TextTransform.to_title_case() text-transform.txt /*TextTransform.to_title_case()* TextTransform.to_words() text-transform.txt /*TextTransform.to_words()* -TextTransform.toggle() text-transform.txt /*TextTransform.toggle()* TextTransform.transform_words() text-transform.txt /*TextTransform.transform_words()* find_word_boundaries() text-transform.txt /*find_word_boundaries()* init() text-transform.txt /*init()* +state.enable() text-transform.txt /*state.enable()* +state.restore_positions() text-transform.txt /*state.restore_positions()* +state.save_positions() text-transform.txt /*state.save_positions()* +state.toggle() text-transform.txt /*state.toggle()* telescope.telescope_popup() text-transform.txt /*telescope.telescope_popup()* utils.dump() text-transform.txt /*utils.dump()* utils.merge() text-transform.txt /*utils.merge()* diff --git a/doc/text-transform.txt b/doc/text-transform.txt index ab5154a..dd3d19c 100644 --- a/doc/text-transform.txt +++ b/doc/text-transform.txt @@ -117,26 +117,26 @@ the full information around the selection logic. ============================================================================== ------------------------------------------------------------------------------ - *TextTransform.toggle()* - `TextTransform.toggle`() + *state.toggle()* + `state.toggle`() Toggle the plugin by calling the `enable`/`disable` methods respectively. @private ------------------------------------------------------------------------------ - *TextTransform.enable()* - `TextTransform.enable`() + *state.enable()* + `state.enable`() Enables the plugin @private ------------------------------------------------------------------------------ - *TextTransform.save_positions()* - `TextTransform.save_positions`() + *state.save_positions()* + `state.save_positions`() Save the current cursor position, mode, and visual selection ranges @private ------------------------------------------------------------------------------ - *TextTransform.restore_positions()* - `TextTransform.restore_positions`({state}) + *state.restore_positions()* + `state.restore_positions`({new_state}) Restore the cursor position, mode, and visual selection ranges saved using `save_position()`, or a given modified state, if passed as the first argument diff --git a/lua/text-transform/commands.lua b/lua/text-transform/commands.lua index 703c351..257f9a9 100644 --- a/lua/text-transform/commands.lua +++ b/lua/text-transform/commands.lua @@ -1,3 +1,4 @@ +-- local D = require("text-transform.util.debug") local state = require("text-transform.state") local replacers = require("text-transform.replacers") local popup = require("text-transform.popup") @@ -16,22 +17,24 @@ function TextTransform.init_commands() TtTitle = "title_case", } + local cmdopts = { range = true, force = true } + for cmd, transformer_name in pairs(map) do vim.api.nvim_create_user_command(cmd, function() state.save_positions() replacers.replace_selection(transformer_name) - end, {}) + end, cmdopts) end -- specific popups vim.api.nvim_create_user_command("TtTelescope", function() local telescope = require("text-transform.telescope") telescope.telescope_popup() - end, {}) + end, cmdopts) vim.api.nvim_create_user_command("TtSelect", function() local select = require("text-transform.select") select.select_popup() - end, {}) + end, cmdopts) -- auto popup by config vim.api.nvim_create_user_command("TextTransform", popup.show_popup, {}) diff --git a/lua/text-transform/replacers.lua b/lua/text-transform/replacers.lua index 14d30a6..a9d8578 100644 --- a/lua/text-transform/replacers.lua +++ b/lua/text-transform/replacers.lua @@ -19,14 +19,18 @@ local function find_word_boundaries(line, start_col) local word_end_col = vim.fn.match(line_text:sub(word_start_col), non_word_pat) + word_start_col - 1 - D.log("replacers", "Found word boundaries: %s", vim.inspect({ word_start_col, word_end_col })) - D.log("replacers", "Word text: %s", line_text:sub(word_start_col, word_end_col)) - D.log("replacers", "Line text: %s", line_text) + D.log( + "find_word_boundaries", + "Found word boundaries: %s", + vim.inspect({ word_start_col, word_end_col }) + ) + D.log("find_word_boundaries", "Word text: %s", line_text:sub(word_start_col, word_end_col)) + D.log("find_word_boundaries", "Line text: %s", line_text) return word_start_col, word_end_col end function TextTransform.replace_range(start_line, start_col, end_line, end_col, transform_name) - D.log("replacers", "Replacing range with %s", transform_name) + D.log("replace_range", "Replacing range with %s", transform_name) local transform = t["to_" .. transform_name] local lines = vim.fn.getline(start_line, end_line) --- @type any local transformed = {} @@ -58,7 +62,7 @@ end --- @param transform_name string The transformer name --- @param position table|nil A table containing the position of the word to replace function TextTransform.replace_word(transform_name, position) - D.log("replacers", "Replacing word with %s", transform_name) + D.log("replace_word", "Replacing word with %s", transform_name) local word, line, col, start_col, end_col if not position then word = vim.fn.expand("") @@ -67,11 +71,11 @@ function TextTransform.replace_word(transform_name, position) start_col, end_col = find_word_boundaries(line, col) word = vim.fn.getline(line):sub(start_col, end_col) end - D.log("replacers", "Found word %s", word) - D.log("replacers", "Using transformer %s", transform_name) + D.log("replace_word", "Found word %s", word) + D.log("replace_word", "Using transformer %s", transform_name) local transformer = t["to_" .. transform_name] local transformed = transformer(word) - D.log("replacers", "New value %s", transformed) + D.log("replace_word", "New value %s", transformed) if not position then vim.cmd("normal ciw" .. transformed) else @@ -83,7 +87,7 @@ end --- Assumes that the each selection is 1 character and operates on the whole word under each cursor. function TextTransform.replace_columns(transform_name) local selections = TextTransform.get_visual_selection_details() - D.log("replacers", "Replacing columns with %s", transform_name) + D.log("replace_columns", "Replacing columns with %s", transform_name) for _, sel in ipairs(selections) do TextTransform.replace_word(transform_name, { 0, sel.start_line, sel.start_col, 0 }) end @@ -95,20 +99,22 @@ end --- --- @param transform_name string The transformer name function TextTransform.replace_selection(transform_name) - D.log("replacers", "Replacing selection with %s", transform_name) + D.log("replace_selection", "Replacing selection with %s", transform_name) -- determine if cursor is a 1-width column across multiple lines or a normal selection -- local start_line, start_col, end_line, end_col = unpack(vim.fn.getpos("'<")) local selections = TextTransform.get_visual_selection_details() - D.log("replacers", "Selections: %s", utils.dump(selections)) + D.log("replace_selection", "Selections: %s", utils.dump(selections)) local is_multiline = #selections > 1 local is_column = is_multiline and selections[1].start_col == selections[#selections].end_col local is_single_cursor = not is_multiline and not is_column + and selections + and selections[1] and selections[1].start_col == selections[1].end_col D.log( - "replacers", + "replace_selection", "is_multiline: %s, is_column: %s, is_word: %s", is_multiline, is_column, @@ -139,19 +145,31 @@ end --- the full information around the selection logic. function TextTransform.get_visual_selection_details() if not state.positions then - D.log("replacers", "No positions saved") + D.log("get_visual_selection_details", "No positions saved") return {} end D.log( - "replacers", + "get_visual_selection_details", "Getting visual selection details - mode: %s, is_visual: %s, is_block: %s", state.positions.mode, utils.is_visual_mode(), utils.is_block_visual_mode() ) + + -- Get the start and end positions of the selection + local start_pos = state.positions.visual_start + local end_pos = state.positions.visual_end + local start_line, start_col = start_pos[2], start_pos[3] + local end_line, end_col = end_pos[2], end_pos[3] + -- Check if currently in visual mode; if not, return the cursor position - if not utils.is_visual_mode() and not utils.is_block_visual_mode() then + if + not utils.is_visual_mode() + and not utils.is_block_visual_mode() + and not state.has_range(start_pos, end_pos) + then local pos = state.positions.pos + D.log("get_visual_selection_details", "Returning single cursor position") return { { start_line = pos[2], @@ -162,12 +180,6 @@ function TextTransform.get_visual_selection_details() } end - -- Get the start and end positions of the selection - local start_pos = state.positions.visual_start - local end_pos = state.positions.visual_end - local start_line, start_col = start_pos[2], start_pos[3] - local end_line, end_col = end_pos[2], end_pos[3] - -- Swap if selection is made upwards or backwards if start_line > end_line or (start_line == end_line and start_col > end_col) then start_line, end_line = end_line, start_line @@ -175,7 +187,7 @@ function TextTransform.get_visual_selection_details() end -- If it's block visual mode, return table for each row - if utils.is_block_visual_mode() then + if utils.is_block_visual_mode() or state.has_range(start_pos, end_pos) then local block_selection = {} for line = start_line, end_line do if start_col == end_col then @@ -189,9 +201,15 @@ function TextTransform.get_visual_selection_details() end_col = start_col, }) end + D.log( + "get_visual_selection_details", + "Returning block selection: %s", + utils.dump(block_selection) + ) return block_selection else -- Normal visual mode, return single table entry + D.log("get_visual_selection_details", "Returning normal selection") return { { start_line = start_line, diff --git a/lua/text-transform/state.lua b/lua/text-transform/state.lua index 7e29c0e..dfeb3b7 100644 --- a/lua/text-transform/state.lua +++ b/lua/text-transform/state.lua @@ -8,7 +8,7 @@ local function ensure_config() end -- methods -local TextTransform = { +local state = { -- Boolean determining if the plugin is enabled or not. enabled = false, -- A table containing cursor position and visual selection details, @@ -18,40 +18,40 @@ local TextTransform = { --- Toggle the plugin by calling the `enable`/`disable` methods respectively. --- @private -function TextTransform.toggle() - if TextTransform.enabled then - return TextTransform.disable() +function state.toggle() + if state.enabled then + return state.disable() end - return TextTransform.enable() + return state.enable() end --- Enables the plugin --- @private -function TextTransform.enable() +function state.enable() ensure_config() - if TextTransform.enabled then - return TextTransform + if state.enabled then + return state end - TextTransform.enabled = true - return TextTransform + state.enabled = true + return state end ---Disables the plugin and reset the internal state. ---@private -function TextTransform.disable() +function state.disable() ensure_config() - if not TextTransform.enabled then - return TextTransform + if not state.enabled then + return state end -- reset the state - TextTransform.enabled = false - TextTransform.positions = nil - return TextTransform + state.enabled = false + state.positions = nil + return state end local function get_mode_type(mode) @@ -64,6 +64,10 @@ local function get_mode_type(mode) return mode_map[mode] or "normal" end +function state.has_range(visual_start, visual_end) + return visual_start and visual_end and visual_start[2] ~= visual_end[2] +end + local function capture_part(start_sel, end_sel, return_type) local l, sel if return_type == "start" then @@ -78,7 +82,7 @@ end --- Save the current cursor position, mode, and visual selection ranges --- @private -function TextTransform.save_positions() +function state.save_positions() local buf = vim.api.nvim_get_current_buf() local mode_info = vim.api.nvim_get_mode() local mode = get_mode_type(mode_info.mode) @@ -92,8 +96,13 @@ function TextTransform.save_positions() D.log("save_positions", "Saved mode %s, cursor %s", mode, vim.inspect(pos)) if mode == "visual" or mode == "line" or mode == "block" then - if mode == "block" then -- for block visual mode - D.log("save_positions", "Visual mode is block, %s", vim.inspect({ visual_start, visual_end })) + if state.has_range(visual_start, visual_end) then -- for ranges + D.log( + "save_positions", + "Visual range, mode is %s, %s", + mode, + vim.inspect({ visual_start, visual_end }) + ) -- Adjust the positions to correctly capture the entire block visual_start = capture_part(visual_start, visual_end, "start") visual_end = capture_part(visual_start, visual_end, "end") @@ -106,7 +115,7 @@ function TextTransform.save_positions() ) end - local state = { + local positions = { buf = buf, mode = mode, pos = pos, @@ -114,32 +123,37 @@ function TextTransform.save_positions() visual_end = visual_end, } - D.log("save_positions", "State: %s", vim.inspect(state)) - TextTransform.positions = state - return state + D.log("save_positions", "State: %s", vim.inspect(positions)) + state.positions = positions + return positions end --- Restore the cursor position, mode, and visual selection ranges saved using `save_position()`, --- or a given modified state, if passed as the first argument -function TextTransform.restore_positions(state) - state = state or TextTransform.positions - vim.api.nvim_set_current_buf(state.buf) - vim.fn.setpos(".", state.pos) - D.log("restore_positions", "Restored mode %s, cursor %s", state.mode, vim.inspect(state.pos)) +function state.restore_positions(new_state) + new_state = new_state or new_state.positions + vim.api.nvim_set_current_buf(new_state.buf) + vim.fn.setpos(".", new_state.pos) + D.log( + "restore_positions", + "Restored mode %s, cursor %s", + new_state.mode, + vim.inspect(new_state.pos) + ) -- Attempt to restore visual mode accurately if - (state.mode == "visual" or state.mode == "block") - and state.visual_start - and state.visual_end + (new_state.mode == "visual" or new_state.mode == "block") + and new_state.visual_start + and new_state.visual_end then - vim.fn.setpos("'<", state.visual_start) - vim.fn.setpos("'>", state.visual_end) + vim.fn.setpos("'<", new_state.visual_start) + vim.fn.setpos("'>", new_state.visual_end) local command = "normal! gv" vim.cmd(command) - D.log("restore_positions", [[Restored visual mode %s using "%s"]], state.mode, command) + D.log("restore_positions", [[Restored visual mode %s using "%s"]], new_state.mode, command) end - TextTransform.positions = nil + new_state.positions = nil end -return TextTransform +return state diff --git a/lua/text-transform/transformers.lua b/lua/text-transform/transformers.lua index 4c80b6f..87991cc 100644 --- a/lua/text-transform/transformers.lua +++ b/lua/text-transform/transformers.lua @@ -47,12 +47,12 @@ function TextTransform.to_words(string) -- Append current character to the current word word = word .. char end - D.log("transformers", "i %d char %s word %s words %s", i, char, word, utils.dump(words)) + -- D.log("to_words", "i %d char %s word %s words %s", i, char, word, utils.dump(words)) end if word ~= "" then table.insert(words, word:lower()) end - D.log("transformers", "words %s", vim.inspect(words)) + D.log("to_words", "words %s", vim.inspect(words)) return words end @@ -75,7 +75,7 @@ function TextTransform.transform_words(words, with_word_cb, separator) new_word = separator .. new_word end out = out .. new_word - D.log("transformers", "word %s (%d) new_word %s out %s", word, i, new_word, out) + D.log("transform_words", "word %s (%d) new_word %s out %s", word, i, new_word, out) end return out end diff --git a/lua/text-transform/util/debug.lua b/lua/text-transform/util/debug.lua index f36d5ac..70ec271 100644 --- a/lua/text-transform/util/debug.lua +++ b/lua/text-transform/util/debug.lua @@ -26,10 +26,10 @@ function D.log(scope, str, ...) print( string.format( - "[text-transform:%s %s in %s] > %s", + "%s [text-transform:%s in %s] > %s", os.date("%H:%M:%S"), - line, scope, + line, string.format(str, ...) ) ) diff --git a/lua/text-transform/util/init.lua b/lua/text-transform/util/init.lua index 0c80a18..4fe4d5d 100644 --- a/lua/text-transform/util/init.lua +++ b/lua/text-transform/util/init.lua @@ -30,4 +30,8 @@ function utils.is_visual_mode() -- return vim.fn.mode() == 'v' end +function utils.has_range(visual_start, visual_end) + return visual_start and visual_end and visual_start[2] ~= visual_end[2] +end + return utils