refactor: new logic, file splitting

BREAKING CHANGES: functions, configs have been renamed
This commit is contained in:
2024-04-28 13:15:29 +03:00
parent ded86d9b0f
commit b4257a50fe
13 changed files with 669 additions and 424 deletions

View File

@@ -9,4 +9,7 @@ community_bridge: # Replace with a single Community Bridge project-name e.g., cl
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: ["https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=TSH3C3ABGQM22&currency_code=ILS&source=url"]
custom:
[
'https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=TSH3C3ABGQM22&currency_code=ILS&source=url',
]

View File

@@ -18,7 +18,7 @@ Transform the current word or selection between multiple case types. Need to eas
with my_var or vice versa? This plugin is for you!
- Works on current word in **Normal Mode**
- Will replace the current word selectable by <kbd>cw</kbd>
- Will replace the current word selectable by <kbd>ciw</kbd>
- Works on selection in **Visual Mode**
- Will replace only inside the selection
- Works on column selections in **Visual Block Mode**

View File

@@ -1,14 +1,14 @@
TextTransform.camel_case() text-transform.txt /*TextTransform.camel_case()*
TextTransform.const_case() text-transform.txt /*TextTransform.const_case()*
TextTransform.dot_case() text-transform.txt /*TextTransform.dot_case()*
TextTransform.into_words() text-transform.txt /*TextTransform.into_words()*
TextTransform.kebab_case() text-transform.txt /*TextTransform.kebab_case()*
TextTransform.options text-transform.txt /*TextTransform.options*
TextTransform.pascal_case() text-transform.txt /*TextTransform.pascal_case()*
TextTransform.replace_at() text-transform.txt /*TextTransform.replace_at()*
TextTransform.replace_columns() text-transform.txt /*TextTransform.replace_columns()*
TextTransform.replace_cursor_range() text-transform.txt /*TextTransform.replace_cursor_range()*
TextTransform.replace_word() text-transform.txt /*TextTransform.replace_word()*
TextTransform.setup() text-transform.txt /*TextTransform.setup()*
TextTransform.snake_case() text-transform.txt /*TextTransform.snake_case()*
TextTransform.title_case() text-transform.txt /*TextTransform.title_case()*
config.options text-transform.txt /*config.options*
config.setup() text-transform.txt /*config.setup()*
fn.replace_columns() text-transform.txt /*fn.replace_columns()*
fn.to_camel_case() text-transform.txt /*fn.to_camel_case()*
fn.to_const_case() text-transform.txt /*fn.to_const_case()*
fn.to_dot_case() text-transform.txt /*fn.to_dot_case()*
fn.to_kebab_case() text-transform.txt /*fn.to_kebab_case()*
fn.to_pascal_case() text-transform.txt /*fn.to_pascal_case()*
fn.to_snake_case() text-transform.txt /*fn.to_snake_case()*
fn.to_title_case() text-transform.txt /*fn.to_title_case()*
fn.to_words() text-transform.txt /*fn.to_words()*
fn.transform_words() text-transform.txt /*fn.transform_words()*
utils.dump() text-transform.txt /*utils.dump()*
utils.merge() text-transform.txt /*utils.merge()*

View File

@@ -1,12 +1,12 @@
==============================================================================
------------------------------------------------------------------------------
*TextTransform.options*
`TextTransform.options`
*config.options*
`config.options`
Your plugin configuration with its default values.
Default values:
>
TextTransform.options = {
config.options = {
-- Prints useful logs about what event are triggered, and reasons actions are executed.
debug = false,
-- Keymap to trigger the transform.
@@ -18,81 +18,125 @@ Default values:
},
}
local function init()
local o = config.options
D.log("config", "Initializing TextTransform with %s", utils.dump(o))
vim.keymap.set("n", o.keymap.n, telescope_popup, { silent = true })
vim.keymap.set("v", o.keymap.v, telescope_popup, { silent = true })
end
<
------------------------------------------------------------------------------
*TextTransform.setup()*
`TextTransform.setup`({options})
*config.setup()*
`config.setup`({options})
Define your text-transform setup.
Parameters~
Parameters ~
{options} `(table)` Module config table. See |TextTransform.options|.
Usage~
Usage ~
`require("text-transform").setup()` (add `{}` with your |TextTransform.options| table)
==============================================================================
------------------------------------------------------------------------------
*TextTransform.into_words()*
`TextTransform.into_words`({str})
Splits a string into words.
------------------------------------------------------------------------------
*TextTransform.camel_case()*
`TextTransform.camel_case`({string})
Transforms a string into camelCase.
------------------------------------------------------------------------------
*TextTransform.snake_case()*
`TextTransform.snake_case`({string})
Transforms a string into snake_case.
------------------------------------------------------------------------------
*TextTransform.pascal_case()*
`TextTransform.pascal_case`({string})
Transforms a string into PascalCase.
------------------------------------------------------------------------------
*TextTransform.kebab_case()*
`TextTransform.kebab_case`({string})
Transforms a string into kebab-case.
------------------------------------------------------------------------------
*TextTransform.dot_case()*
`TextTransform.dot_case`({string})
Transforms a string into dot.case.
------------------------------------------------------------------------------
*TextTransform.title_case()*
`TextTransform.title_case`({string})
Transforms a string into Title Case.
------------------------------------------------------------------------------
*TextTransform.const_case()*
`TextTransform.const_case`({string})
Transforms a string into CONSTANT_CASE.
------------------------------------------------------------------------------
*TextTransform.replace_at()*
`TextTransform.replace_at`({start_line}, {start_col}, {end_line}, {end_col}, {transform})
Replaces the text at the given position with the given transform.
------------------------------------------------------------------------------
*TextTransform.replace_word()*
`TextTransform.replace_word`({transform})
Replaces the current word with the given transform.
------------------------------------------------------------------------------
*TextTransform.replace_columns()*
`TextTransform.replace_columns`({transform})
*fn.replace_columns()*
`fn.replace_columns`({transform_name})
Replaces each column in visual block mode selection with the given transform.
Assumes that the each selection is 1 character and operates on the whole word under each cursor.
==============================================================================
------------------------------------------------------------------------------
*TextTransform.replace_cursor_range()*
`TextTransform.replace_cursor_range`({line}, {start_col}, {end_col}, {transform})
Replaces each cursor selection range with the given transform.
*fn.to_words()*
`fn.to_words`({string})
Splits a string into words.
@param string string
@return table
------------------------------------------------------------------------------
*fn.transform_words()*
`fn.transform_words`({words}, {with_word_cb}, {separator})
Transforms a table of strings into a string using a callback and separator.
The callback is called with the word, the index, and the table of words.
The separator is added between each word.
@param words table of strings
@param with_word_cb function (word: string, index: number, words: table) -> string
@param separator string|nil (optional)
@return string
------------------------------------------------------------------------------
*fn.to_camel_case()*
`fn.to_camel_case`({string})
Transforms a string into camelCase.
@param string string
@return string
------------------------------------------------------------------------------
*fn.to_snake_case()*
`fn.to_snake_case`({string})
Transfroms a string into snake_case.
@param string any
@return string
------------------------------------------------------------------------------
*fn.to_pascal_case()*
`fn.to_pascal_case`({string})
Transforms a string into PascalCase.
@param string string
@return string
------------------------------------------------------------------------------
*fn.to_title_case()*
`fn.to_title_case`({string})
Transforms a string into Title Case.
@param string string
@return string
------------------------------------------------------------------------------
*fn.to_kebab_case()*
`fn.to_kebab_case`({string})
Transforms a string into kebab-case.
@param string string
@return string
------------------------------------------------------------------------------
*fn.to_dot_case()*
`fn.to_dot_case`({string})
Transforms a string into dot.case.
@param string string
@return string
------------------------------------------------------------------------------
*fn.to_const_case()*
`fn.to_const_case`({string})
Transforms a string into CONSTANT_CASE.
@param string string
@return string
==============================================================================
------------------------------------------------------------------------------
*utils.merge()*
`utils.merge`({t1}, {t2})
Merges two tables into one. Same as `vim.tbl_extend("keep", t1, t2)`.
Mutates the first table.
TODO accept multiple tables to merge
@param t1 table
@param t2 table
@return table
------------------------------------------------------------------------------
*utils.dump()*
`utils.dump`({obj})
Dumps the object into a string.
@param obj any
@return string
vim:tw=78:ts=8:noet:ft=help:norl:

View File

@@ -1,10 +1,13 @@
local TextTransform = {}
local telescope_popup = require("text-transform.telescope")
local D = require("text-transform.util.debug")
local utils = require("text-transform.util")
local config = {}
--- Your plugin configuration with its default values.
---
--- Default values:
---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
TextTransform.options = {
config.options = {
-- Prints useful logs about what event are triggered, and reasons actions are executed.
debug = false,
-- Keymap to trigger the transform.
@@ -16,71 +19,33 @@ TextTransform.options = {
},
}
local function init()
local o = config.options
D.log("config", "Initializing TextTransform with %s", utils.dump(o))
vim.keymap.set("n", o.keymap.n, telescope_popup, { silent = true })
vim.keymap.set("v", o.keymap.v, telescope_popup, { silent = true })
end
--- Define your text-transform setup.
---
---@param options table Module config table. See |TextTransform.options|.
---
---@usage `require("text-transform").setup()` (add `{}` with your |TextTransform.options| table)
function TextTransform.setup(options)
function config.setup(options)
options = options or {}
TextTransform.options = vim.tbl_deep_extend("keep", options, TextTransform.options)
config.options = utils.merge(config.options, options)
if vim.api.nvim_get_vvar("vim_did_enter") == 0 then
vim.defer_fn(function()
TextTransform._setup()
init()
end, 0)
else
TextTransform._setup()
init()
end
return TextTransform.options
return config.options
end
local CAMEL_CASE = "&camelCase"
local SNAKE_CASE = "&snake_case"
local PASCAL_CASE = "&PascalCase"
local KEBAB_CASE = "&kebab-case"
local DOT_CASE = "&dot\\.case"
local TITLE_CASE = "&Title\\ Case"
local CONST_CASE = "C&ONST_CASE"
-- TODO save frequency of use and order by frequency
local default_ordered_keys =
{ CAMEL_CASE, SNAKE_CASE, PASCAL_CASE, CONST_CASE, KEBAB_CASE, DOT_CASE, TITLE_CASE }
function TextTransform._setup()
local map = {
[CAMEL_CASE] = "camel_case",
[SNAKE_CASE] = "snake_case",
[PASCAL_CASE] = "pascal_case",
[KEBAB_CASE] = "kebab_case",
[DOT_CASE] = "dot_case",
[TITLE_CASE] = "title_case",
[CONST_CASE] = "const_case",
}
---@diagnostic disable-next-line: unused-local
for _i, k in pairs(default_ordered_keys) do
local v = map[k]
vim.cmd("amenu TransformsWord." .. k .. " :lua TextTransform.replace_word('" .. v .. "')<CR>")
vim.cmd(
"amenu TransformsSelection." .. k .. " :lua TextTransform.replace_columns('" .. v .. "')<CR>"
)
end
vim.keymap.set(
"n",
TextTransform.options.keymap.n,
"<cmd>popup TransformsWord<CR>",
{ silent = true }
)
vim.keymap.set(
"v",
TextTransform.options.keymap.v,
"<cmd>popup TransformsSelection<CR>",
{ silent = true }
)
end
return TextTransform
return config

View File

@@ -1,54 +1,11 @@
local M = require("text-transform.main")
local TextTransform = {}
-- Toggle the plugin by calling the `enable`/`disable` methods respectively.
function TextTransform.toggle()
-- when the config is not set to the global object, we set it
if _G.TextTransform.config == nil then
_G.TextTransform.config = require("text-transform.config").options
end
_G.TextTransform.state = M.toggle()
end
-- starts TextTransform and set internal functions and state.
function TextTransform.enable()
if _G.TextTransform.config == nil then
_G.TextTransform.config = require("text-transform.config").options
end
local state = M.enable()
if state ~= nil then
_G.TextTransform.state = state
end
return state
end
-- disables TextTransform and reset internal functions and state.
function TextTransform.disable()
_G.TextTransform.state = M.disable()
end
local D = require("text-transform.util.debug")
local TextTransform = require("text-transform.main")
-- setup TextTransform options and merge them with user provided ones.
function TextTransform.setup(opts)
_G.TextTransform.config = require("text-transform.config").setup(opts)
end
TextTransform.into_words = M.into_words
TextTransform.replace_word = M.replace_word
TextTransform.replace_selection = M.replace_selection
TextTransform.replace_columns = M.replace_columns
TextTransform.camel_case = M.camel_case
TextTransform.snake_case = M.snake_case
TextTransform.pascal_case = M.pascal_case
TextTransform.kebab_case = M.kebab_case
TextTransform.dot_case = M.dot_case
TextTransform.title_case = M.title_case
TextTransform.const_case = M.const_case
_G.TextTransform = TextTransform
return _G.TextTransform

View File

@@ -1,252 +1,16 @@
local D = require("text-transform.util.debug")
local utils = require("text-transform.util")
local tt = require("text-transform.transformers")
local replacers = require("text-transform.replacers")
local state = require("text-transform.state")
-- internal methods
local TextTransform = {}
-- state
local S = {
-- Boolean determining if the plugin is enabled or not.
enabled = false,
}
---Toggle the plugin by calling the `enable`/`disable` methods respectively.
---@private
function TextTransform.toggle()
if S.enabled then
return TextTransform.disable()
end
return TextTransform.enable()
local function merge(table)
TextTransform = utils.merge(TextTransform, table)
end
---Initializes the plugin.
---@private
function TextTransform.enable()
if S.enabled then
return S
end
S.enabled = true
return S
end
---Disables the plugin and reset the internal state.
---@private
function TextTransform.disable()
if not S.enabled then
return S
end
-- reset the state
S = {
enabled = false,
}
return S
end
--- Splits a string into words.
function TextTransform.into_words(str)
local words = {}
local word = ""
local previous_is_split_token = false
for i = 1, #str do
local char = str:sub(i, i)
local is_upper = char:match("%u")
local is_num = char:match("%d")
local is_separator = char:match("[%_%-%s%.]")
local is_split_token = is_upper or is_num
-- split on uppercase letters or numbers
if is_split_token and not previous_is_split_token then
if word ~= "" then
table.insert(words, word:lower())
end
previous_is_split_token = true
word = char
-- split on underscores, hyphens, and spaces
elseif is_separator then
if word ~= "" then
table.insert(words, word:lower())
previous_is_split_token = false
end
word = ""
else
word = word .. char
previous_is_split_token = is_split_token
end
end
if word ~= "" then
table.insert(words, word:lower())
previous_is_split_token = false
end
return words
end
--- Transforms a string into camelCase.
function TextTransform.camel_case(string)
local words = TextTransform.into_words(string)
local camel_case = ""
for i, word in ipairs(words) do
if i == 1 then
camel_case = camel_case .. word:lower()
else
camel_case = camel_case .. word:sub(1, 1):upper() .. word:sub(2):lower()
end
end
return camel_case
end
--- Transforms a string into snake_case.
function TextTransform.snake_case(string)
local words = TextTransform.into_words(string)
local snake_case = ""
for i, word in ipairs(words) do
if i == 1 then
snake_case = snake_case .. word:lower()
else
snake_case = snake_case .. "_" .. word:lower()
end
end
return snake_case
end
--- Transforms a string into PascalCase.
function TextTransform.pascal_case(string)
local words = TextTransform.into_words(string)
local pascal_case = ""
for _, word in ipairs(words) do
pascal_case = pascal_case .. word:sub(1, 1):upper() .. word:sub(2):lower()
end
return pascal_case
end
--- Transforms a string into kebab-case.
function TextTransform.kebab_case(string)
local words = TextTransform.into_words(string)
local kebab_case = ""
for i, word in ipairs(words) do
if i == 1 then
kebab_case = kebab_case .. word:lower()
else
kebab_case = kebab_case .. "-" .. word:lower()
end
end
return kebab_case
end
--- Transforms a string into dot.case.
function TextTransform.dot_case(string)
local words = TextTransform.into_words(string)
local dot_case = ""
for i, word in ipairs(words) do
if i == 1 then
dot_case = dot_case .. word:lower()
else
dot_case = dot_case .. "." .. word:lower()
end
end
return dot_case
end
--- Transforms a string into Title Case.
function TextTransform.title_case(string)
local words = TextTransform.into_words(string)
local title_case = ""
for i, word in ipairs(words) do
title_case = title_case .. word:sub(1, 1):upper() .. word:sub(2):lower()
if i ~= #words then
title_case = title_case .. " "
end
end
return title_case
end
--- Transforms a string into CONSTANT_CASE.
function TextTransform.const_case(string)
local words = TextTransform.into_words(string)
local const_case = ""
for i, word in ipairs(words) do
if i == 1 then
const_case = const_case .. word:upper()
else
const_case = const_case .. "_" .. word:upper()
end
end
return const_case
end
--- Replaces the text at the given position with the given transform.
function TextTransform.replace_at(start_line, start_col, end_line, end_col, transform)
-- use the arguments to replace at the position
local lines = vim.fn.getline(start_line, end_line)
local transformed = ""
if #lines == 1 then
transformed = lines[1]:sub(1, start_col - 1)
.. TextTransform[transform](lines[1]:sub(start_col, end_col))
.. lines[1]:sub(end_col + 1)
else
transformed = lines[1]:sub(1, start_col - 1)
.. TextTransform[transform](lines[1]:sub(start_col))
.. "\n"
for i = 2, #lines - 1 do
transformed = transformed .. TextTransform[transform](lines[i]) .. "\n"
end
transformed = transformed
.. TextTransform[transform](lines[#lines]:sub(1, end_col))
.. lines[#lines]:sub(end_col + 1)
end
-- replace the lines with the transformed lines
vim.fn.setline(start_line, transformed)
for i = start_line + 1, end_line do
vim.fn.setline(i, "")
end
end
--- Replaces the current word with the given transform.
function TextTransform.replace_word(transform)
local word = vim.fn.expand("<cword>")
local transformed = TextTransform[transform](word)
vim.cmd("normal ciw" .. transformed)
end
--- Replaces each column in visual block mode selection with the given transform.
--- Assumes that the each selection is 1 character and operates on the whole word under each cursor.
function TextTransform.replace_columns(transform)
-- get all the multiple cursor positions
local _, start_line, start_col = unpack(vim.fn.getpos("'<"))
local _, end_line = unpack(vim.fn.getpos("'>"))
D.tprint({ start_line, start_col, end_line })
-- for each cursor start_col, find the word under the cursor and transform it
for line_num = start_line, end_line do
-- get the line of this cursor
local line = vim.fn.getline(line_num)
-- match the surrounding word using start_col
local word = line:match("[%w%_%-]+", start_col)
-- replace the word with the transformed word
TextTransform.replace_cursor_range(line_num, start_col, start_col + #word - 1, transform)
end
end
--- Replaces each cursor selection range with the given transform.
function TextTransform.replace_cursor_range(line, start_col, end_col, transform)
return TextTransform.replace_at(line, start_col, line, end_col, transform)
end
function TextTransform.replace_selection(transform)
-- determine if cursor is a 1-length column or a normal selection
local is_column = vim.fn.getpos("'<")[2] == vim.fn.getpos("'>")[2]
if is_column then
TextTransform.replace_columns(transform)
else
local _, start_line, start_col, end_col = unpack(vim.fn.getpos("'<"))
TextTransform.replace_cursor_range(start_line, start_col, end_col, transform)
end
end
merge(tt)
merge(replacers)
TextTransform.state = state
return TextTransform

View File

@@ -0,0 +1,164 @@
local D = require("text-transform.util.debug")
local state = require("text-transform.state")
local utils = require("text-transform.util")
local t = require("text-transform.transformers")
local fn = {}
local function find_word_boundaries(line, start_col)
local line_text = vim.fn.getline(line)
-- dashes, underscores, and periods are considered part of a word
local word_pat = "[A-Za-z0-9_.\\-]"
local non_word_pat = "[^A-Za-z0-9_.\\-]"
local word_start_col = vim.fn.match(line_text:sub(start_col), word_pat) + 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)
return word_start_col, word_end_col
end
function fn.replace_range(start_line, start_col, end_line, end_col, transform_name)
D.log("replacers", "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 = {}
if #lines == 1 then
local line = lines[1]
local before = line:sub(0, start_col - 1)
local fixed = transform(line:sub(start_col, end_col))
local after = line:sub(end_col + 1)
table.insert(transformed, before .. fixed .. after)
else
for i, line in ipairs(lines) do
if i == 1 then
table.insert(transformed, line:sub(1, start_col - 1) .. transform(line:sub(start_col)))
elseif i == #lines then
table.insert(transformed, transform(line:sub(1, end_col)) .. line:sub(end_col + 1))
else
table.insert(transformed, transform(line))
end
end
end
vim.fn.setline(start_line, transformed)
end
function fn.replace_word(transform_name, position)
D.log("replacers", "Replacing word with %s", transform_name)
local word, line, col, start_col, end_col
if not position then
word = vim.fn.expand("<cword>")
else
_, line, col = unpack(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)
local transformer = t['to_' .. transform_name]
local transformed = transformer(word)
D.log("replacers", "New value %s", transformed)
if not position then
vim.cmd("normal ciw" .. transformed)
else
fn.replace_range(line, start_col, line, end_col, transform_name)
end
end
--- Replaces each column in visual block mode selection with the given transform.
--- Assumes that the each selection is 1 character and operates on the whole word under each cursor.
function fn.replace_columns(transform_name)
local selections = fn.get_visual_selection_details()
D.log("replacers", "Replacing columns with %s", transform_name)
for _, sel in ipairs(selections) do
fn.replace_word(transform_name, { 0, sel.start_line, sel.start_col, 0 })
-- TODO replace with replace_word? if so, need to find how to get the word under a given position
-- fn.replace_range(sel.start_line, sel.start_col, sel.end_line, sel.end_col, transform_name)
end
end
function fn.replace_selection(transform_name)
D.log("replacers", "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 = fn.get_visual_selection_details()
D.log("replacers", "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[1].start_col == selections[1].end_col
D.log("replacers", "is_multiline: %s, is_column: %s, is_word: %s", is_multiline, is_column, is_single_cursor)
if is_single_cursor then
fn.replace_word(transform_name)
elseif is_column then
fn.replace_columns(transform_name)
else
for _, sel in pairs(selections) do
fn.replace_range(sel.start_line, sel.start_col, sel.end_line, sel.end_col, transform_name)
end
end
end
function fn.get_visual_selection_details()
D.log(
"replacers", "Getting visual selection details - mode: %s, is_visual: %s, is_block: %s",
state.positions.mode, utils.is_visual_mode(), utils.is_block_visual_mode()
)
-- 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
local pos = state.positions.pos
return {
{
start_line = pos[2],
end_line = pos[2],
start_col = pos[3],
end_col = pos[3]
}
}
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
start_col, end_col = end_col, start_col
end
-- If it's block visual mode, return table for each row
if utils.is_block_visual_mode() then
local block_selection = {}
for line = start_line, end_line do
if start_col == end_col then
-- find the word surrounding the position
start_col, _ = find_word_boundaries(line, start_col)
end
table.insert(block_selection, {
start_line = line,
end_line = line,
start_col = start_col,
end_col = start_col,
})
end
return block_selection
else
-- Normal visual mode, return single table entry
return {
{
start_line = start_line,
end_line = end_line,
start_col = start_col,
end_col = end_col
}
}
end
end
return fn

View File

@@ -0,0 +1,120 @@
local D = require("text-transform.util.debug")
local function ensure_config()
-- when the config is not set to the global object, we set it
if _G.TextTransform.config == nil then
_G.TextTransform.config = require("text-transform.config").options
end
end
-- methods
local State = {
-- Boolean determining if the plugin is enabled or not.
enabled = false,
positions = nil,
}
---Toggle the plugin by calling the `enable`/`disable` methods respectively.
---@private
function State.toggle()
if State.enabled then
return State.disable()
end
return State.enable()
end
---Initializes the plugin.
---@private
function State.enable()
ensure_config()
if State.enabled then
return State
end
State.enabled = true
return State
end
---Disables the plugin and reset the internal state.
---@private
function State.disable()
ensure_config()
if not State.enabled then
return State
end
-- reset the state
State.enabled = false
State.positions = nil
return State
end
local function get_mode_type(mode)
-- classify mode as either visual, line, block or normal
local mode_map = {
['v'] = 'visual',
['V'] = 'line',
['\22'] = 'block',
}
return mode_map[mode] or 'normal'
end
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)
local pos = vim.fn.getcurpos()
-- leave mode
local esc = vim.api.nvim_replace_termcodes("<esc>", true, false, true)
vim.api.nvim_feedkeys(esc, 'x', true)
local visual_start = vim.fn.getpos("'<")
local visual_end = vim.fn.getpos("'>")
D.log("popup_menu", "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("popup_menu", "Visual mode is block, %s", vim.inspect({ visual_start, visual_end }))
-- Adjust the positions to correctly capture the entire block
visual_start = { visual_start[1], math.min(visual_start[2], visual_end[2]), visual_start[3], visual_start[4] }
visual_end = { visual_end[1], math.max(visual_start[2], visual_end[2]), visual_end[3], visual_end[4] }
end
D.log("popup_menu", "Saved visual mode %s, cursor %s", mode, vim.inspect({ visual_start, visual_end }))
end
local state = {
buf = buf,
mode = mode,
pos = pos,
visual_start = visual_start,
visual_end = visual_end
}
D.log("popup_menu", "State: %s", vim.inspect(state))
State.positions = state
return state
end
function State.restore_positions(state)
state = state or State.positions
vim.api.nvim_set_current_buf(state.buf)
vim.fn.setpos('.', state.pos)
D.log("popup_menu", "Restored mode %s, cursor %s", state.mode, vim.inspect(state.pos))
-- Attempt to restore visual mode accurately
if (state.mode == 'visual' or state.mode == 'block') and state.visual_start and state.visual_end then
vim.fn.setpos("'<", state.visual_start)
vim.fn.setpos("'>", state.visual_end)
local command = 'normal! gv'
vim.cmd(command)
D.log("popup_menu", [[Restored visual mode %s using "%s"]], state.mode, command)
end
State.positions = nil
end
return State

View File

@@ -0,0 +1,65 @@
local TextTransform = require("text-transform.main")
local state = require("text-transform.state")
local D = require("text-transform.util.debug")
local pickers = require "telescope.pickers"
local finders = require "telescope.finders"
local conf = require("telescope.config").values
local actions = require "telescope.actions"
local action_state = require "telescope.actions.state"
local dropdown = require("telescope.themes").get_dropdown({})
local items = {
{ label = "camelCase", value = "camel_case" },
{ label = "snake_case", value = "snake_case" },
{ label = "PascalCase", value = "pascal_case" },
{ label = "kebab-case", value = "kebab_case" },
{ label = "dot.case", value = "dot_case" },
{ label = "Title Case", value = "title_case" },
{ label = "CONST_CASE", value = "const_case" },
}
---@diagnostic disable-next-line: unused-local
-- for _i, k in pairs(default_ordered_keys) do
-- local v = map[k]
-- vim.cmd("amenu TransformsWord." .. k .. " :lua TextTransform.replace_word('" .. v .. "')<CR>")
-- vim.cmd(
-- "amenu TransformsSelection." .. k .. " :lua TextTransform.replace_columns('" .. v .. "')<CR>"
-- )
-- end
local popup_menu = function()
state.save_positions()
local picker = pickers.new(dropdown, {
prompt_title = 'Change Case',
finder = finders.new_table {
results = items,
entry_maker = function(entry)
return {
value = entry.value,
display = entry.label,
ordinal = entry.label
}
end
},
sorter = conf.generic_sorter({}),
attach_mappings = function(prompt_bufnr)
actions.select_default:replace(function()
local selection = action_state.get_selected_entry()
actions.close(prompt_bufnr)
vim.schedule(function()
TextTransform.replace_selection(selection.value)
state.restore_positions()
end)
end)
return true
end,
})
vim.schedule(function()
picker:find()
end)
end
return popup_menu

View File

@@ -0,0 +1,124 @@
local D = require("text-transform.util.debug")
local utils = require("text-transform.util")
local fn = {}
fn.WORD_BOUNDRY = '[%_%-%s%.]'
--- Splits a string into words.
--- @param string string
--- @return table
function fn.to_words(string)
local words = {}
local word = ''
for i = 1, #string do
local char = string:sub(i, i)
if char:match(fn.WORD_BOUNDRY) then
if word ~= '' then
table.insert(words, word:lower())
end
word = ''
else
word = word .. char
-- word = word .. string:sub(i)
-- break
end
D.log('transformers', '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))
return words
end
--- Transforms a table of strings into a string using a callback and separator.
--- The callback is called with the word, the index, and the table of words.
--- The separator is added between each word.
---
--- @param words table of strings
--- @param with_word_cb function (word: string, index: number, words: table) -> string
--- @param separator string|nil (optional)
--- @return string
function fn.transform_words(words, with_word_cb, separator)
local out = ''
for i, word in ipairs(words) do
local new_word = with_word_cb(word, i, word)
out = out .. new_word
if separator and i > 1 then
out = out .. separator
end
D.log('transformers', 'word %s new_word %s out %s', word, new_word, out)
end
return out
end
--- Transforms a string into camelCase.
--- @param string string
--- @return string
function fn.to_camel_case(string)
return fn.transform_words(fn.to_words(string), function(word, i)
if i == 1 then
return word:lower()
end
return word:sub(1, 1):upper() .. word:sub(2):lower()
end)
end
--- Transfroms a string into snake_case.
--- @param string any
--- @return string
function fn.to_snake_case(string)
return fn.transform_words(fn.to_words(string), function(word, i)
if i == 1 then
return word:lower()
end
return word:lower()
end, '_')
end
--- Transforms a string into PascalCase.
--- @param string string
--- @return string
function fn.to_pascal_case(string)
local cc = fn.to_camel_case(string)
return cc:sub(1, 1):upper() .. cc:sub(2)
end
--- Transforms a string into Title Case.
--- @param string string
--- @return string
function fn.to_title_case(string)
return fn.transform_words(fn.to_words(string), function(word)
return word:sub(1, 1):upper() .. word:sub(2):lower()
end, ' ')
end
--- Transforms a string into kebab-case.
--- @param string string
--- @return string
function fn.to_kebab_case(string)
return fn.transform_words(fn.to_words(string), function(word)
return word:lower()
end, '-')
end
--- Transforms a string into dot.case.
--- @param string string
--- @return string
function fn.to_dot_case(string)
return fn.transform_words(fn.to_words(string), function(word)
return word:lower()
end, '.')
end
--- Transforms a string into CONSTANT_CASE.
--- @param string string
--- @return string
function fn.to_const_case(string)
return fn.transform_words(fn.to_words(string), function(word)
return word:upper()
end, '_')
end
return fn

View File

@@ -1,5 +1,11 @@
local D = {}
local function is_debug()
return _G.TextTransform ~= nil and
_G.TextTransform.config ~= nil and
_G.TextTransform.config.debug
end
---prints only if debug is true.
---
---@param scope string: the scope from where this function is called.
@@ -7,7 +13,7 @@ local D = {}
---@param ... any: the arguments of the formatted string.
---@private
function D.log(scope, str, ...)
if _G.TextTransform.config ~= nil and not _G.TextTransform.config.debug then
if not is_debug() then
return
end
@@ -35,7 +41,7 @@ end
---@param indent number?: the default indent value, starts at 0.
---@private
function D.tprint(table, indent)
if _G.TextTransform.config ~= nil and not _G.TextTransform.config.debug then
if not is_debug() then
return
end

View File

@@ -0,0 +1,33 @@
local state = require("text-transform.state")
local utils = {}
--- Merges two tables into one. Same as `vim.tbl_extend("keep", t1, t2)`.
--- Mutates the first table.
---
--- TODO accept multiple tables to merge
---
--- @param t1 table
--- @param t2 table
--- @return table
function utils.merge(t1, t2)
return vim.tbl_extend("force", t1, t2)
end
--- Dumps the object into a string.
--- @param obj any
--- @return string
function utils.dump(obj)
return vim.inspect(obj)
end
function utils.is_block_visual_mode()
return state.positions.mode == 'block'
-- return vim.fn.mode() == "V" or vim.fn.mode() == "\22"
end
function utils.is_visual_mode()
return state.positions.mode == 'visual'
-- return vim.fn.mode() == 'v'
end
return utils